Repository: GoogleChromeLabs/ps-analysis-tool
Pull request #97 (2023-08-04 06:05:58)

Refactor: Increase test coverage.
## Description
This PR aims to introduce tests to increase testing coverage in the develop branch. Various test cases have been introduced to add test coverage to utilities and components. 

## Testing Instructions

Note: This PR doesn't introduce any UI changes so it doesn't need any QA.
- Open the terminal and run `npm run test`.
- Now run `npm run test:coverage` you can see a generated test coverage which covers 85% of the project.



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 c01fbc9b..4591eaf6 100644
--- a/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/accordion/index.tsx
@@ -79,6 +79,7 @@ const Accordion: React.FC<AccordionProps> = ({
         onKeyDown={(event) => keyboardNavigator(event)}
       >
         <div
+          data-testid="accordion-opener"
           className={`origin-center transition-transform scale-125 p-0.5 ${
             accordionState ? '' : '-rotate-90'
           }`}
@@ -90,7 +91,7 @@ const Accordion: React.FC<AccordionProps> = ({
             <ArrowDown />
           )}
         </div>
-        <div className="pl-1">
+        <div>
           {selectedIndex === index && !selectedFrame ? (
             <CookieWhite />
           ) : (
diff --git a/packages/extension/src/view/devtools/components/sidebar/index.tsx b/packages/extension/src/view/devtools/components/sidebar/index.tsx
index f92d10f4..c2e7d8c3 100644
--- a/packages/extension/src/view/devtools/components/sidebar/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/index.tsx
@@ -57,11 +57,12 @@ const Sidebar: React.FC<SidebarProps> = ({
   const keyboardNavigator = useCallback(
     // eslint-disable-next-line complexity
     (event: React.KeyboardEvent<HTMLDivElement>) => {
-      if (!tabFrames) {
-        return;
+      let keys: string[] = [];
+      let currIndex = 0;
+      if (tabFrames) {
+        keys = Object.keys(tabFrames);
+        currIndex = keys.findIndex((frame) => frame === selectedFrame);
       }
-      const keys = Object.keys(tabFrames);
-      const currIndex = keys.findIndex((frame) => frame === selectedFrame);
       switch (event.code) {
         case 'ArrowUp':
           if (accordionState) {
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 0d14267a..5820f91d 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
+            data-testid="show-url-decoded-checkbox"
             role="checkbox"
             type="checkbox"
             className="ml-3 mr-1 cursor-pointer"


Test patch:
diff --git a/packages/extension/src/utils/tests/countCookiesByCategory.ts b/packages/extension/src/utils/tests/countCookiesByCategory.ts
index b0544829..d899ea09 100644
--- a/packages/extension/src/utils/tests/countCookiesByCategory.ts
+++ b/packages/extension/src/utils/tests/countCookiesByCategory.ts
@@ -144,6 +144,12 @@ describe('countCookiesByCategory : ', () => {
     );
   });
 
+  it('Gives empty stats with null passed as cookies', () => {
+    expect(countCookiesByCategory(null, 'https://example.com/page')).toEqual(
+      EMPTY_STATS
+    );
+  });
+
   it('Generates correct tests', () => {
     const cookies = {
       [functional1pCookie.parsedCookie.name]: functional1pCookie,
diff --git a/packages/extension/src/utils/tests/getTab.ts b/packages/extension/src/utils/tests/getTab.ts
new file mode 100644
index 00000000..5115df02
--- /dev/null
+++ b/packages/extension/src/utils/tests/getTab.ts
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+/**
+ * Internal dependencies.
+ */
+import { getTab } from '../getTab';
+const dummyTab = {
+  active: false,
+  audible: false,
+  autoDiscardable: true,
+  discarded: false,
+  favIconUrl: 'file://dummyFolder/picture.png',
+  groupId: -1,
+  height: 976,
+  highlighted: false,
+  id: 478098967,
+  incognito: false,
+  index: 6,
+  mutedInfo: {
+    muted: false,
+  },
+  pinned: false,
+  selected: false,
+  status: 'complete',
+  title: 'Test page',
+  url: 'file://dummyFolder/index.html',
+  width: 1920,
+  windowId: 478098000,
+};
+describe('getTab : ', () => {
+  beforeAll(() => {
+    //@ts-ignore
+    globalThis.chrome = {
+      tabs: {
+        //@ts-ignore
+        get: (tabId: number) => {
+          if (tabId === 478098967) {
+            return dummyTab;
+          } else if (tabId === 478098969) {
+            return null;
+          }
+          throw new Error('Cannot get tab for this id');
+        },
+      },
+      //@ts-ignore
+      runtime: {},
+    };
+    jest.spyOn(console, 'log').mockImplementation(() => undefined);
+  });
+
+  it('Should not return tab when tabId is negative', async () => {
+    expect(await getTab(-1)).toBe(null);
+  });
+
+  it('Should return correct tab when tabId is provided', async () => {
+    expect(await getTab(478098967)).toBe(dummyTab);
+  });
+
+  it('Should not return tab when correct tabId is not provided', async () => {
+    expect(await getTab(478098969)).toBe(null);
+  });
+
+  it('Should throw error when incorrect arguments are passed', async () => {
+    await getTab(4780989);
+    // eslint-disable-next-line no-console
+    expect(console.log).toHaveBeenCalled();
+  });
+});
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 d9b22e2d..294f86ea 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
@@ -18,6 +18,7 @@
  */
 import React from 'react';
 import { render, screen, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import '@testing-library/jest-dom';
 
 /**
@@ -121,6 +122,7 @@ describe('Accordion', () => {
 
     expect(framesContainer).toHaveClass('hidden');
   });
+
   it('should not display tabFrames if not opened.', () => {
     const renderedAccordion = render(
       <Accordion
@@ -170,4 +172,29 @@ describe('Accordion', () => {
     expect(firstFrame).not.toHaveClass('bg-royal-blue');
     expect(secondFrame).toHaveClass('bg-royal-blue');
   });
+
+  it('should call keyboard navigator function.', () => {
+    const keyboardNavigatorMock = jest.fn();
+    render(
+      <Accordion
+        keyboardNavigator={keyboardNavigatorMock}
+        accordionState={true}
+        setAccordionState={() => undefined}
+        tabName={'Cookies'}
+        index={1}
+        selectedIndex={1}
+        tabFrames={{
+          'https://edition.cnn.com': { frameIds: [1] },
+          'https://crxd.net': { frameIds: [2] },
+          'https://pubmatic.com': { frameIds: [3] },
+        }}
+        setSelectedFrame={() => undefined}
+        selectedFrame={null}
+        setIndex={() => undefined}
+      />
+    );
+    userEvent.tab();
+    userEvent.keyboard('{ArrowDown}');
+    expect(keyboardNavigatorMock).toHaveBeenCalled();
+  });
 });
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 1b3cad7d..561d3036 100644
--- a/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx
+++ b/packages/extension/src/view/devtools/components/sidebar/tests/index.tsx
@@ -19,6 +19,7 @@
 import React from 'react';
 import { render, screen, fireEvent } from '@testing-library/react';
 import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
 import { type Cookie as ParsedCookie } from 'simple-cookie';
 
 /**
@@ -236,7 +237,7 @@ describe('Sidebar', () => {
     expect(mainFrame).toHaveClass('bg-royal-blue');
   });
 
-  it('should select another menu and close cookie accordion', () => {
+  it('should select another menu and unselect cookie accordion', () => {
     mockUseCookieStore.mockReturnValueOnce({
       cookies: mockResponse.tabCookies,
       tabUrl: mockResponse.tabUrl,
@@ -281,4 +282,603 @@ describe('Sidebar', () => {
     expect(cookieHeaderContainer).not.toHaveClass('bg-royal-blue');
     expect(attributionContainer).toHaveClass('bg-royal-blue');
   });
+
+  it('should close accordion and deselect the frame.', () => {
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    const accordionOpener = screen.getByTestId('accordion-opener');
+    fireEvent.click(accordionOpener);
+    expect(accordionOpener).not.toHaveClass('-rotate-90');
+
+    fireEvent.click(screen.getByTestId('https://edition.cnn.com/'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+
+    fireEvent.click(accordionOpener);
+    expect(accordionOpener).toHaveClass('-rotate-90');
+  });
+
+  it('Left Arrow should close accordion', () => {
+    render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on the cookie heading
+    userEvent.tab();
+    // Simulate right arrow key down
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    // Simulate left arrow key down
+    userEvent.keyboard('{ArrowLeft}');
+    expect(screen.getByTestId('accordion-opener')).toHaveClass('-rotate-90');
+  });
+
+  it('Left Arrow should unselect selected frame and select the Cookies menu', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Should focus on cookies menu
+    userEvent.tab();
+    //Simulate right arrow key down
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Simulate down arrow key down
+    userEvent.keyboard('{ArrowDown}');
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate left arrow key down
+    userEvent.keyboard('{ArrowLeft}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('cookies-tab-heading-wrapper')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Down Arrow should select next frame', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Focus on the cookies menu
+    userEvent.tab();
+    //Simulate right arrow key keydown to open accordion
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Simulate down arrow key keydown to select frame
+    userEvent.keyboard('{ArrowDown}');
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate down arrow key keydown to select next frame
+    userEvent.keyboard('{ArrowDown}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://crxt.net/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('https://crxt.net/')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Up Arrow should select previous frame', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Focus on cookie menu header
+    userEvent.tab();
+    //Simulate right arrow key keydown to simulate accordion opening.
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Simulate down arrow key keydown to select first frame
+    userEvent.keyboard('{ArrowDown}');
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate down arrow key keydown to select next frame
+    userEvent.keyboard('{ArrowDown}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://crxt.net/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('https://crxt.net/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate up arrow key keydown to select previous frame
+    userEvent.keyboard('{ArrowUp}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('https://crxt.net/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Up Arrow should unselect selected frame and select the Cookies menu', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Focus on cookie menu header
+    userEvent.tab();
+    //Simulate right arrow key keydown to simulate accordion opening.
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    //Simulate down arrow key keydown to select first frame
+    userEvent.keyboard('{ArrowDown}');
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate up arrow key keydown to select cookie menu header
+    userEvent.keyboard('{ArrowUp}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('cookies-tab-heading-wrapper')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Down Arrow on last frame should select the next main menu tab.', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.tab();
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+
+    fireEvent.click(screen.getByTestId('https://crxt.net/'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://crxt.net/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://crxt.net/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    //Simulate arrow down key to go to Topics menu
+    userEvent.keyboard('{ArrowDown}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={1}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://crxt.net/')).not.toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('Topics')).toHaveClass('bg-royal-blue');
+  });
+
+  it('Up Arrow on Topics menu should select last frame in accordion.', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.tab();
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    // Click on Topics tab
+    fireEvent.click(screen.getByTestId('Topics'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={1}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Topics')).toHaveClass('bg-royal-blue');
+    //Simulate arrow up to select last frame in the list.
+    userEvent.keyboard('{ArrowUp}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://crxt.net/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://crxt.net/')).toHaveClass(
+      'bg-royal-blue'
+    );
+    expect(screen.getByTestId('Topics')).not.toHaveClass('bg-royal-blue');
+  });
+
+  it('Up Arrow on Attribution menu should select Topics Menu.', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.tab();
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    // Click on Attribution tab
+    fireEvent.click(screen.getByTestId('Attribution'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={2}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Attribution')).toHaveClass('bg-royal-blue');
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.keyboard('{ArrowUp}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={1}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Topics')).toHaveClass('bg-royal-blue');
+    expect(screen.getByTestId('Attribution')).not.toHaveClass('bg-royal-blue');
+  });
+
+  it('Down Arrow on cookies menu with open accordion should select first frame.', () => {
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.tab();
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    //Simulate arrow down key to select first frame.
+    userEvent.keyboard('{ArrowDown}');
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: 'https://edition.cnn.com/',
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('https://edition.cnn.com/')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Down Arrow on Topics menu should select Attribution Menu.', () => {
+    const sidebarRender = render(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={0}
+        setIndex={() => undefined}
+      />
+    );
+    // Focus on cookie header menu and simulate right arrow to open accordion
+    userEvent.tab();
+    userEvent.keyboard('{ArrowRight}');
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    // Click on Topics tab
+    fireEvent.click(screen.getByTestId('Topics'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={1}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Topics')).toHaveClass('bg-royal-blue');
+    // Simulate down arrow to go to next menu.
+    userEvent.keyboard('{ArrowDown}');
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={2}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Attribution')).toHaveClass('bg-royal-blue');
+    expect(screen.getByTestId('Topics')).not.toHaveClass('bg-royal-blue');
+  });
+
+  it('Down Arrow on Topics menu should select Attribution Menu.', () => {
+    const setIndexMock = jest.fn();
+    const sidebarRender = render(
+      <Sidebar tabsNames={tabNames} selectedIndex={0} setIndex={setIndexMock} />
+    );
+    // Click on accordion opener button.
+    fireEvent.click(screen.getByTestId('accordion-opener'));
+
+    expect(screen.getByTestId('accordion-opener')).not.toHaveClass(
+      '-rotate-90'
+    );
+    // Click on Topics menu
+    fireEvent.click(screen.getByTestId('Topics'));
+
+    mockUseCookieStore.mockReturnValueOnce({
+      cookies: mockResponse.tabCookies,
+      tabUrl: mockResponse.tabUrl,
+      tabFrames: mockResponse.tabFrames,
+      selectedFrame: null,
+      setSelectedFrame: mockResponse.setSelectedFrame,
+    });
+    sidebarRender.rerender(
+      <Sidebar
+        tabsNames={tabNames}
+        selectedIndex={1}
+        setIndex={() => undefined}
+      />
+    );
+    expect(screen.getByTestId('Topics')).toHaveClass('bg-royal-blue');
+
+    fireEvent.click(screen.getByTestId('https://crxt.net/'));
+    // Check if setIndex was called.
+    expect(setIndexMock).toHaveBeenCalled();
+  });
 });
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 f044ff6e..fac7adf1 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
@@ -71,6 +71,13 @@ const known3pCookie: ParsedCookie = {
   domain: '.pubmatic.com',
 };
 
+const known3pCookieWithValue: ParsedCookie = {
+  ...emptyCookie,
+  name: 'KRTBCOOKIE_290',
+  value: 'known3p_Cookie-with%20value',
+  domain: '.pubmatic.com',
+};
+
 const mockResponse: {
   tabCookies: NonNullable<CookieStoreContext['state']['tabCookies']>;
   tabUrl: NonNullable<CookieStoreContext['state']['tabUrl']>;
@@ -140,6 +147,27 @@ const mockResponse: {
       isCookieSet: true,
       frameIdList: [1],
     },
+    [known3pCookieWithValue.name]: {
+      parsedCookie: known3pCookieWithValue,
+      analytics: {
+        platform: 'PubMatic',
+        category: 'Marketing',
+        name: 'KRTBCOOKIE_*',
+        domain: 'pubmatic.com',
+        description:
+          "Registers a unique ID that identifies the user's device during return visits across websites that use the same ad network. The ID is used to allow targeted ads.",
+        retention: '29 days',
+        dataController: 'Pubmatic',
+        gdprUrl: 'N/A',
+        wildcard: '1',
+      },
+      url: 'https://api.pubmatic.com/whatever/api',
+      headerType: 'response',
+      isFirstParty: false,
+      isIbcCompliant: true,
+      isCookieSet: true,
+      frameIdList: [1],
+    },
   },
   tabUrl: 'https://edition.cnn.com/',
   tabFrames: {
@@ -399,4 +427,32 @@ describe('CookieTab', () => {
         mockResponse.tabCookies[uncategorised1pCookie.name],
     });
   });
+
+  it('should decode the cookie value on clicking checkbox', async () => {
+    const lastCookie =
+      mockResponse.tabCookies[Object.keys(mockResponse.tabCookies)[3]];
+
+    mockUseContentPanelStore.mockReturnValue({
+      selectedFrameCookie: {
+        1: mockResponse.tabCookies[known3pCookieWithValue.name],
+      },
+      tableContainerRef: { current: null },
+      tableColumnSize: 100,
+      setTableColumnSize: jest.fn(),
+    });
+
+    render(<CookieDetails />);
+    const card = await screen.findByTestId('cookie-card');
+
+    expect(card).toBeInTheDocument();
+    expect(
+      await within(card).findByText(lastCookie.parsedCookie.value)
+    ).toBeInTheDocument();
+
+    fireEvent.click(screen.getByTestId('show-url-decoded-checkbox'));
+
+    expect(
+      await within(card).findByText('known3p_Cookie-with value')
+    ).toBeInTheDocument();
+  });
 });
diff --git a/packages/extension/src/view/devtools/tests/app.tsx b/packages/extension/src/view/devtools/tests/app.tsx
index 6085fb21..d75ec6eb 100644
--- a/packages/extension/src/view/devtools/tests/app.tsx
+++ b/packages/extension/src/view/devtools/tests/app.tsx
@@ -18,6 +18,8 @@
  */
 import React from 'react';
 import { render, screen, fireEvent } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import userEvent from '@testing-library/user-event';
 import '@testing-library/jest-dom';
 import SinonChrome from 'sinon-chrome';
 
@@ -43,14 +45,14 @@ describe('App', () => {
     } as unknown as typeof fetch;
   });
 
-  it('Should show cookies content by default', () => {
-    render(<App />);
+  it('Should show cookies content by default', async () => {
+    await act(() => render(<App />));
 
     expect(screen.getByTestId('cookies-content')).toBeInTheDocument();
   });
 
   it('should switch to cookie panel when tab is clicked', async () => {
-    render(<App />);
+    await act(() => render(<App />));
     // Move to another tab
     fireEvent.click(screen.getByText('Bounce Tracking'));
 
@@ -59,7 +61,7 @@ describe('App', () => {
   });
 
   it('should switch to Bounce Tracking Panel when clicked', async () => {
-    render(<App />);
+    await act(() => render(<App />));
     // Click on Bounce Tracking tab
     fireEvent.click(screen.getByText('Bounce Tracking'));
 
@@ -69,7 +71,7 @@ describe('App', () => {
   });
 
   it('should switch to FingerPrinting Panel when clicked', async () => {
-    render(<App />);
+    await act(() => render(<App />));
     // Click on FingerPrinting tab
     fireEvent.click(screen.getByText('Fingerprinting'));
 
@@ -78,6 +80,60 @@ describe('App', () => {
     ).toBeInTheDocument();
   });
 
+  it('should switch to Attribution Panel when clicked', async () => {
+    await act(() => render(<App />));
+    // Click on Attribution tab
+    fireEvent.click(screen.getByText('Attribution'));
+
+    expect(
+      await screen.findByTestId('attribution-content')
+    ).toBeInTheDocument();
+  });
+
+  it('should switch to Topics Panel when clicked', async () => {
+    await act(() => render(<App />));
+
+    // Click on Topics tab
+    fireEvent.click(screen.getByText('Topics'));
+
+    expect(await screen.findByTestId('topics-content')).toBeInTheDocument();
+  });
+
+  it('Down Keyboard navigation should work.', async () => {
+    await act(() => render(<App />));
+    // Focus on the first menu item.
+    userEvent.tab();
+    // Press arrow down
+    userEvent.keyboard('{ArrowDown}');
+    expect(await screen.findByTestId('Topics')).toHaveClass('bg-royal-blue');
+  });
+
+  it('Up Keyboard navigation should work.', async () => {
+    await act(() => render(<App />));
+    // Focus on the first menu item.
+    userEvent.tab();
+    // Press arrow down to go to next menu
+    userEvent.keyboard('{ArrowDown}');
+    // Press arrow down to go to previous menu
+    userEvent.keyboard('{ArrowUp}');
+    expect(screen.getByTestId('cookies-tab-heading-wrapper')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
+  it('Up Keyboard navigation should work.', async () => {
+    await act(() => render(<App />));
+    // Focus on the first menu item.
+    userEvent.tab();
+    expect(screen.getByTestId('cookies-tab-heading-wrapper')).toHaveClass(
+      'bg-royal-blue'
+    );
+    userEvent.keyboard('{Enter}');
+    expect(screen.getByTestId('cookies-tab-heading-wrapper')).toHaveClass(
+      'bg-royal-blue'
+    );
+  });
+
   afterAll(() => {
     globalThis.chrome = undefined as unknown as typeof chrome;
     globalThis.fetch = undefined as unknown as typeof fetch;
diff --git a/packages/extension/src/view/devtools/tests/index.tsx b/packages/extension/src/view/devtools/tests/index.tsx
new file mode 100644
index 00000000..22a58a2f
--- /dev/null
+++ b/packages/extension/src/view/devtools/tests/index.tsx
@@ -0,0 +1,220 @@
+/*
+ * 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 { render, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import '@testing-library/jest-dom';
+import SinonChrome from 'sinon-chrome';
+
+/**
+ * Internal dependencies.
+ */
+import App from '../app';
+import { Provider as ExternalStoreProvider } from '../stateProviders/syncCookieStore';
+import { Provider as ContentPanelProvider } from '../stateProviders/contentPanelStore';
+// @ts-ignore
+// eslint-disable-next-line import/no-unresolved
+import PSInfo from 'cookie-analysis-tool/data/PSInfo.json';
+
+const tabCookies = {
+  _cb: {
+    parsedCookie: '_cb',
+    analytics: null,
+    url: 'https://edition.cnn.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: true,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  pubsyncexp: {
+    parsedCookie: 'pubsyncexp',
+    analytics: null,
+    url: 'https://api.pubmatic.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: false,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  __qca: {
+    parsedCookie: '__qca',
+    analytics: {
+      platform: 'Quantcast',
+      category: 'Marketing',
+      name: '__qca',
+      domain: "Advertiser's website domain",
+      description:
+        'This cookie is set by Quantcast, who present targeted advertising. Stores browser and HTTP request information.',
+      retention: '1 year',
+      dataController: 'Quantcast',
+      gdprUrl: 'https://www.quantcast.com/privacy/',
+      wildcard: '0',
+    },
+    url: 'https://edition.cnn.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: true,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  KRTBCOOKIE_290: {
+    parsedCookie: 'KRTBCOOKIE_290',
+    analytics: {
+      platform: 'PubMatic',
+      category: 'Marketing',
+      name: 'KRTBCOOKIE_*',
+      domain: 'pubmatic.com',
+      description:
+        "Registers a unique ID that identifies the user's device during return visits across websites that use the same ad network. The ID is used to allow targeted ads.",
+      retention: '29 days',
+      dataController: 'Pubmatic',
+      gdprUrl: 'N/A',
+      wildcard: '1',
+    },
+    url: 'https://api.pubmatic.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: false,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+};
+
+describe('Index', () => {
+  beforeAll(() => {
+    globalThis.chrome = SinonChrome as unknown as typeof chrome;
+    globalThis.chrome = {
+      ...SinonChrome,
+      webNavigation: {
+        //@ts-ignore
+        getAllFrames: () => {
+          return [
+            {
+              processId: 1,
+              frameId: 0,
+              url: 'https://edition.cnn.com',
+              documentId: '40245632AWDER',
+              documentLifecycle: 'active',
+              errorOccurred: false,
+              frameType: 'outermost_frame',
+              parentFrameId: 0,
+            },
+            {
+              processId: 1,
+              frameId: 2,
+              url: 'https://crxd.net',
+              documentId: '40245632WADER',
+              documentLifecycle: 'active',
+              errorOccurred: false,
+              frameType: 'sub_frame',
+              parentFrameId: 0,
+            },
+            {
+              processId: 1,
+              frameId: 3,
+              url: 'https://edition.cnn.com',
+              documentId: '40245632AWDER',
+              documentLifecycle: 'active',
+              errorOccurred: false,
+              frameType: 'sub_frame',
+              parentFrameId: 0,
+            },
+          ];
+        },
+      },
+      storage: {
+        //@ts-ignore
+        local: {
+          //@ts-ignore
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          get: (_, __) =>
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            new Promise<{ [key: string]: any }>((resolve) => {
+              resolve({ 40245632: { cookies: tabCookies } });
+            }),
+          //@ts-ignore
+          onChanged: {
+            addListener: () => undefined,
+            removeListener: () => undefined,
+          },
+        },
+      },
+      devtools: {
+        //@ts-ignore
+        inspectedWindow: {
+          tabId: 40245632,
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
+          eval: (_, callback: any) => {
+            callback('https://edition.cnn.com');
+          },
+        },
+      },
+      tabs: {
+        //@ts-ignore
+        onUpdated: {
+          addListener: () => undefined,
+          removeListener: () => undefined,
+        },
+      },
+    };
+    globalThis.fetch = function () {
+      return Promise.resolve({
+        json: () =>
+          Promise.resolve({
+            ...PSInfo,
+          }),
+      });
+    } as unknown as typeof fetch;
+  });
+
+  it('Both Providers should be added to DOM', async () => {
+    act(() =>
+      render(
+        <ExternalStoreProvider>
+          <ContentPanelProvider>
+            <App />
+          </ContentPanelProvider>
+        </ExternalStoreProvider>
+      )
+    );
+    expect(
+      await screen.findByTestId('cookies-tab-heading-wrapper')
+    ).toHaveClass('bg-royal-blue');
+  });
+
+  it('Content Panel Provider should be added to DOM', async () => {
+    act(() =>
+      render(
+        <ExternalStoreProvider>
+          <ContentPanelProvider>
+            <App />
+          </ContentPanelProvider>
+        </ExternalStoreProvider>
+      )
+    );
+    expect(
+      await screen.findByTestId('cookies-tab-heading-wrapper')
+    ).toHaveClass('bg-royal-blue');
+  });
+  afterAll(() => {
+    globalThis.chrome = undefined as unknown as typeof chrome;
+    globalThis.fetch = undefined as unknown as typeof fetch;
+  });
+});
diff --git a/packages/extension/src/view/popup/tests/app.tsx b/packages/extension/src/view/popup/tests/app.tsx
new file mode 100644
index 00000000..e7aebf6a
--- /dev/null
+++ b/packages/extension/src/view/popup/tests/app.tsx
@@ -0,0 +1,173 @@
+/*
+ * 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 { render, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import '@testing-library/jest-dom';
+import SinonChrome from 'sinon-chrome';
+
+/**
+ * Internal dependencies.
+ */
+import App from '../app';
+import { Provider as ExternalStoreProvider } from '../stateProviders/syncCookieStore';
+
+const tabCookies = {
+  _cb: {
+    parsedCookie: '_cb',
+    analytics: null,
+    url: 'https://edition.cnn.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: true,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  pubsyncexp: {
+    parsedCookie: 'pubsyncexp',
+    analytics: null,
+    url: 'https://api.pubmatic.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: false,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  __qca: {
+    parsedCookie: '__qca',
+    analytics: {
+      platform: 'Quantcast',
+      category: 'Marketing',
+      name: '__qca',
+      domain: "Advertiser's website domain",
+      description:
+        'This cookie is set by Quantcast, who present targeted advertising. Stores browser and HTTP request information.',
+      retention: '1 year',
+      dataController: 'Quantcast',
+      gdprUrl: 'https://www.quantcast.com/privacy/',
+      wildcard: '0',
+    },
+    url: 'https://edition.cnn.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: true,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+  KRTBCOOKIE_290: {
+    parsedCookie: 'KRTBCOOKIE_290',
+    analytics: {
+      platform: 'PubMatic',
+      category: 'Marketing',
+      name: 'KRTBCOOKIE_*',
+      domain: 'pubmatic.com',
+      description:
+        "Registers a unique ID that identifies the user's device during return visits across websites that use the same ad network. The ID is used to allow targeted ads.",
+      retention: '29 days',
+      dataController: 'Pubmatic',
+      gdprUrl: 'N/A',
+      wildcard: '1',
+    },
+    url: 'https://api.pubmatic.com/whatever/api',
+    headerType: 'response',
+    frameIdList: [1],
+    isFirstParty: false,
+    isIbcCompliant: true,
+    isCookieSet: true,
+  },
+};
+
+describe('App', () => {
+  describe('When Tab ID is available', () => {
+    beforeAll(() => {
+      globalThis.chrome = SinonChrome as unknown as typeof chrome;
+      globalThis.chrome = {
+        ...SinonChrome,
+        storage: {
+          //@ts-ignore
+          local: {
+            //@ts-ignore
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
+            get: (_, __) =>
+              // eslint-disable-next-line @typescript-eslint/no-explicit-any
+              new Promise<{ [key: string]: any }>((resolve) => {
+                resolve({ 40245632: { cookies: tabCookies } });
+              }),
+            //@ts-ignore
+            onChanged: {
+              addListener: () => undefined,
+              removeListener: () => undefined,
+            },
+          },
+        },
+        devtools: {
+          //@ts-ignore
+          inspectedWindow: {
+            tabId: 40245632,
+            //@ts-ignore
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
+            eval: (_, callback: any) => {
+              callback('https://edition.cnn.com');
+            },
+          },
+        },
+        tabs: {
+          //@ts-ignore
+          onUpdated: {
+            addListener: () => undefined,
+            removeListener: () => undefined,
+          },
+          //@ts-ignore
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          query: (_, __) => {
+            return [{ id: 40245632, url: 'https://edition.cnn.com' }];
+          },
+        },
+      };
+    });
+
+    it('ExternalStoreProvider should be added to DOM', async () => {
+      act(() =>
+        render(
+          <ExternalStoreProvider>
+            <App />
+          </ExternalStoreProvider>
+        )
+      );
+      expect(await screen.findByText('1st Party Cookies')).toBeInTheDocument();
+      expect(await screen.findByText('3rd Party Cookies')).toBeInTheDocument();
+    });
+
+    it('Initial render will show please refresh page to see cookies', () => {
+      render(
+        <ExternalStoreProvider>
+          <App />
+        </ExternalStoreProvider>
+      );
+      expect(
+        screen.getByText('Please refresh this page to view cookies')
+      ).toBeInTheDocument();
+    });
+  });
+
+  afterAll(() => {
+    globalThis.chrome = undefined as unknown as typeof chrome;
+    globalThis.fetch = undefined as unknown as typeof fetch;
+  });
+});
diff --git a/packages/extension/src/view/popup/tests/index.tsx b/packages/extension/src/view/popup/tests/index.tsx
new file mode 100644
index 00000000..870d5819
--- /dev/null
+++ b/packages/extension/src/view/popup/tests/index.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import SinonChrome from 'sinon-chrome';
+
+/**
+ * Internal dependencies.
+ */
+import App from '../app';
+import { useCookieStore } from '../stateProviders/syncCookieStore';
+// @ts-ignore
+// eslint-disable-next-line import/no-unresolved
+import PSInfo from 'cookie-analysis-tool/data/PSInfo.json';
+
+jest.mock('../stateProviders/syncCookieStore', () => ({
+  useCookieStore: jest.fn(),
+}));
+
+const mockUseCookieStore = useCookieStore as jest.Mock;
+
+describe('App', () => {
+  beforeAll(() => {
+    globalThis.chrome = SinonChrome as unknown as typeof chrome;
+
+    globalThis.fetch = function () {
+      return Promise.resolve({
+        json: () =>
+          Promise.resolve({
+            ...PSInfo,
+          }),
+      });
+    } as unknown as typeof fetch;
+  });
+
+  it('Should show refresh page message if cookie stats are not available', () => {
+    mockUseCookieStore.mockReturnValueOnce({
+      tabCookieStats: {},
+    });
+    render(<App />);
+
+    expect(
+      screen.getByText('Please refresh this page to view cookies')
+    ).toBeInTheDocument();
+  });
+
+  it('Should show No cookies found on this page message if no firstParty and thirdParty cookies are not available', () => {
+    mockUseCookieStore.mockReturnValueOnce({
+      cookieStats: {
+        total: 0,
+        firstParty: {
+          total: 0,
+        },
+        thirdParty: {
+          total: 0,
+        },
+      },
+    });
+    render(<App />);
+
+    expect(
+      screen.getByText('No cookies found on this page')
+    ).toBeInTheDocument();
+  });
+
+  it('Should show No cookies found on this page message if no firstParty and thirdParty cookies are not available', () => {
+    mockUseCookieStore.mockReturnValueOnce({
+      cookieStats: {
+        total: 6,
+        firstParty: {
+          total: 3,
+          analytics: 1,
+          marketing: 1,
+          functional: 1,
+          uncategorised: 0,
+        },
+        thirdParty: {
+          total: 3,
+          analytics: 1,
+          marketing: 1,
+          functional: 1,
+          uncategorised: 0,
+        },
+      },
+    });
+    render(<App />);
+
+    expect(screen.getAllByText('3').length).toBe(2);
+    expect(screen.getByText('1st Party Cookies')).toBeInTheDocument();
+    expect(screen.getByText('3rd Party Cookies')).toBeInTheDocument();
+  });
+
+  afterAll(() => {
+    globalThis.chrome = undefined as unknown as typeof chrome;
+    globalThis.fetch = undefined as unknown as typeof fetch;
+  });
+});
diff --git a/packages/extension/src/worker/tests/fetchPreviousCookieDataObject.ts b/packages/extension/src/worker/tests/fetchPreviousCookieDataObject.ts
new file mode 100644
index 00000000..b04d210b
--- /dev/null
+++ b/packages/extension/src/worker/tests/fetchPreviousCookieDataObject.ts
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/*
+ * Internal dependencies
+ */
+import { findPreviousCookieDataObject } from '../findPreviousCookieDataObject';
+
+describe('findPreviousCookieDataObject', () => {
+  beforeAll(() => {
+    //@ts-ignore
+    globalThis.chrome = {
+      storage: {
+        //@ts-ignore
+        local: {
+          //@ts-ignore
+          get: () => {
+            throw Error('cannot get');
+          },
+        },
+      },
+    };
+  });
+
+  it('Should return null if cookie is not stored.', async () => {
+    expect(await findPreviousCookieDataObject('478098564', '1P_JARS')).toBe(
+      null
+    );
+  });
+});
diff --git a/tests/jest.config.js b/tests/jest.config.js
index 66df1240..c93eede6 100644
--- a/tests/jest.config.js
+++ b/tests/jest.config.js
@@ -47,6 +47,10 @@ export default {
     '<rootDir>/dist',
     '<rootDir>/third_party',
     '<rootDir>/data',
+    '/stories/',
+    '<rootDir>/packages/extension/src/view/devtools/index.tsx',
+    '<rootDir>/packages/extension/src/view/popup/index.tsx',
+    '<rootDir>/packages/extension/src/view/devtools/devtools.ts',
   ],
   coverageReporters: ['lcov'],
   collectCoverageFrom: [