diff --git a/manifest.json b/manifest.json index 16c93fa..3a9dd8a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "QueryCraft", - "version": "1.0.2", + "version": "1.0.8", "description": "Inspect, edit, save, and replay URL query parameters on the active tab.", "action": { "default_popup": "src/popup/index.html", diff --git a/package-lock.json b/package-lock.json index ba0a606..3de4f4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "querycraft", - "version": "1.0.0", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "querycraft", - "version": "1.0.0", + "version": "1.0.8", "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/package.json b/package.json index 6d5d40b..7e6af30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "querycraft", - "version": "1.0.0", + "version": "1.0.8", "description": "Inspect, edit, save, and replay URL query parameters on the active tab.", "private": true, "type": "module", diff --git a/src/__tests__/GroupedLinksList.test.tsx b/src/__tests__/GroupedLinksList.test.tsx new file mode 100644 index 0000000..7d71b8c --- /dev/null +++ b/src/__tests__/GroupedLinksList.test.tsx @@ -0,0 +1,45 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { GroupedLinksList } from '@/components/SavedLinksDrawer/GroupedLinksList'; +import type { Group, SavedLink } from '@/types'; + +const group: Group = { id: 'g1', name: 'Unsorted', createdAt: 0 }; +const link: SavedLink = { + id: 'l1', + url: 'https://example.com/?foo=bar', + label: 'Example', + createdAt: 0, + groupId: 'g1', +}; + +function renderList(onLoadLink = vi.fn()) { + render( + , + ); + return onLoadLink; +} + +describe('GroupedLinksList', () => { + it('loads the saved URL when its name is clicked', async () => { + const onLoadLink = renderList(); + + await userEvent.click(screen.getByText('Example')); + + expect(onLoadLink).toHaveBeenCalledWith('https://example.com/?foo=bar'); + }); + + it('exposes the name as a button so it can be reached by keyboard', () => { + renderList(); + + expect(screen.getByRole('button', { name: /^Example/ })).toBeInTheDocument(); + }); +}); diff --git a/src/__tests__/useActiveTabUrl.test.ts b/src/__tests__/useActiveTabUrl.test.ts index 0538e7e..3cff16d 100644 --- a/src/__tests__/useActiveTabUrl.test.ts +++ b/src/__tests__/useActiveTabUrl.test.ts @@ -52,6 +52,21 @@ describe('useActiveTabUrl', () => { }); }); + it('keeps the tab id when Chrome hides the URL (e.g. a new tab page)', async () => { + vi.mocked(chrome.tabs.query).mockResolvedValueOnce([ + { id: 3, active: true } as chrome.tabs.Tab, + ]); + + renderHook(() => useActiveTabUrl()); + + await waitFor(() => { + expect(useAppStore.getState().tabState.status).toBe('unsupported'); + }); + + // The tab id is what lets a saved link be loaded from this tab. + expect(useAppStore.getState().tabState).toMatchObject({ status: 'unsupported', tabId: 3 }); + }); + it('sets error status when chrome.tabs.query rejects', async () => { vi.mocked(chrome.tabs.query).mockRejectedValueOnce(new Error('Permission denied')); diff --git a/src/components/SavedLinksDrawer/GroupedLinksList.tsx b/src/components/SavedLinksDrawer/GroupedLinksList.tsx index 4887480..38b066f 100644 --- a/src/components/SavedLinksDrawer/GroupedLinksList.tsx +++ b/src/components/SavedLinksDrawer/GroupedLinksList.tsx @@ -83,10 +83,15 @@ export function GroupedLinksList({ const name = link.label || link.url; return (
  • - + { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); - if (!tab || tab.id === undefined || !tab.url) return null; - return { id: tab.id, url: tab.url }; + if (!tab || tab.id === undefined) return null; + // Keep the id even without a URL so a saved link can still be loaded into this tab. + return { id: tab.id, url: tab.url ?? '' }; }, async updateUrl(tabId: number, url: string): Promise { diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 174eaaf..1aee8f7 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -24,28 +24,31 @@ /* Surfaces */ --color-bg: oklch(0.15 0 0); /* popup panel */ --color-bg-outer: oklch(0.1 0 0); /* page behind the panel */ - --color-surface-inset: oklch(0.19 0 0); /* inputs, url box, saved items */ - --color-surface-header: oklch(0.18 0 0); /* card header strips */ + --color-surface-inset: oklch(0.22 0 0); /* inputs, url box, saved items */ + --color-surface-header: oklch(0.21 0 0); /* card header strips */ - /* Text — descending emphasis */ + /* + * Text — descending emphasis. Every step, faint included, stays at or above + * 4.5:1 on bg, surface-inset and surface-header (the README's AA rule). + */ --color-text-primary: oklch(0.95 0 0); --color-text-strong: oklch(0.9 0 0); /* param keys, active values */ --color-text-body: oklch(0.94 0 0); /* editable input text */ --color-text-secondary: oklch(0.75 0 0); /* outlined button labels */ - --color-text-muted: oklch(0.55 0 0); /* section labels, meta */ - --color-text-faint: oklch(0.45 0 0); /* placeholders, idle icons */ + --color-text-muted: oklch(0.67 0 0); /* section labels, meta */ + --color-text-faint: oklch(0.62 0 0); /* placeholders, idle icons */ /* Borders — white at low alpha */ - --color-border-subtle: oklch(1 0 0 / 0.06); - --color-border: oklch(1 0 0 / 0.08); - --color-border-card: oklch(1 0 0 / 0.09); - --color-border-strong: oklch(1 0 0 / 0.12); + --color-border-subtle: oklch(1 0 0 / 0.08); + --color-border: oklch(1 0 0 / 0.1); + --color-border-card: oklch(1 0 0 / 0.12); + --color-border-strong: oklch(1 0 0 / 0.16); --color-border-focus: oklch(1 0 0 / 0.3); /* Fills for hover / inset controls */ - --color-fill-subtle: oklch(1 0 0 / 0.06); - --color-fill: oklch(1 0 0 / 0.08); - --color-fill-strong: oklch(1 0 0 / 0.1); + --color-fill-subtle: oklch(1 0 0 / 0.08); + --color-fill: oklch(1 0 0 / 0.1); + --color-fill-strong: oklch(1 0 0 / 0.14); /* * Primary action — inverted block by default. When an accent color is @@ -81,12 +84,12 @@ /* JSON syntax — monochrome, separated by lightness + weight */ --json-key: oklch(0.95 0 0); - --json-index: oklch(0.6 0 0); + --json-index: oklch(0.66 0 0); --json-string: oklch(0.8 0 0); --json-number: oklch(0.9 0 0); --json-boolean: oklch(0.85 0 0); - --json-null: oklch(0.55 0 0); - --json-punct: oklch(0.5 0 0); + --json-null: oklch(0.64 0 0); + --json-punct: oklch(0.62 0 0); } /* ── Light ────────────────────────────────────────────────────────── */