Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .eslintignore

This file was deleted.

44 changes: 0 additions & 44 deletions .eslintrc.json

This file was deleted.

1 change: 1 addition & 0 deletions __tests__/components/common/TabToggleWithOverflow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ describe('TabToggleWithOverflow', () => {
await waitFor(() =>
expect(moreButton).toHaveAttribute('aria-expanded', 'true')
);
await screen.findByRole('menuitem', { name: 'C' });
});

it('indicates overflow active state via data attribute when opened', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ it("calls on select and shows checkmark when selected", () => {
throw new Error("List item not found");
}
expect(listItem).toHaveAttribute("data-option-id", "profile-search-item-0x1");
expect(listItem.querySelector("svg")).toBeInTheDocument();
expect(listItem.id).toBe("profile-search-item-0x1-visual");
expect(listItem.querySelector('[data-icon="check"]')).not.toBeNull();
fireEvent.click(listItem);
expect(onSelect).toHaveBeenCalled();
expect(onSelect).toHaveBeenCalledWith(profile);
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ test('calls setSelected on click', () => {
const setSelected = jest.fn();
const item = { label: 'Item', value: 'v', key: 'k' };
render(<CommonDropdownItem item={item} activeItem="" setSelected={setSelected} isMobile={false} />);
fireEvent.click(screen.getByRole('menuitem', { name: 'Item' }));
fireEvent.click(screen.getByRole('menuitem', { name: /Item/ }));
expect(setSelected).toHaveBeenCalledWith('v');
});

test('shows check icon when active', () => {
const item = { label: 'Item', value: 'v', key: 'k' };
render(<CommonDropdownItem item={item} activeItem="v" setSelected={jest.fn()} isMobile={false} sortDirection={SortDirection.ASC} />);
const menuItem = screen.getByRole('menuitem', { name: /Item/ });
expect(screen.getByTestId('sort')).toBeInTheDocument();
expect(screen.getByTestId('sort')).toHaveTextContent('ASC');
expect(screen.getByRole('menuitem', { name: /Item/ }).querySelector('svg')).toBeInTheDocument();
expect(menuItem.querySelector('[data-icon="check"]')).not.toBeNull();
});

test('handles copy feedback', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,44 @@ import { AuthContext } from '@/components/auth/Auth';
import { ReactQueryWrapperContext } from '@/components/react-query-wrapper/ReactQueryWrapper';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

jest.mock('@tanstack/react-query', () => ({
useMutation: jest.fn(),
useQuery: jest.fn(),
useQueryClient: jest.fn(),
}));

jest.mock('@/components/waves/specs/groups/group/edit/WaveGroupEditButton', () => ({
__esModule: true,
default: React.forwardRef(({ onWaveUpdate, renderTrigger }: any, ref: any) => {
const handleOpen = () => onWaveUpdate({});
if (typeof ref === 'function') {
ref({ open: handleOpen });
} else if (ref) {
ref.current = { open: handleOpen };
}
if (renderTrigger === null) {
return null;
}
return renderTrigger ? <>{renderTrigger({ open: handleOpen })}</> : <button onClick={handleOpen}>edit</button>;
}),
}));
jest.mock('@tanstack/react-query', () => {
const actual = jest.requireActual('@tanstack/react-query');
return {
...actual,
useMutation: jest.fn(),
useQuery: jest.fn(),
useQueryClient: jest.fn(),
};
});
jest.mock('@/components/waves/specs/groups/group/edit/WaveGroupEditButton', () => {
const React = require('react');
return {
__esModule: true,
default: React.forwardRef(({ onWaveUpdate, renderTrigger }: any, ref) => {
const handleOpen = () => onWaveUpdate({});
React.useImperativeHandle(ref, () => ({ open: handleOpen }), [handleOpen]);
if (renderTrigger === null) {
return null;
}
return renderTrigger ? <>{renderTrigger({ open: handleOpen })}</> : <button onClick={handleOpen}>edit</button>;
}),
};
});

jest.mock('@/components/waves/specs/groups/group/edit/WaveGroupRemoveButton', () => ({
__esModule: true,
default: React.forwardRef(({ onWaveUpdate, renderTrigger }: any, ref: any) => {
const handleOpen = () => onWaveUpdate({});
if (typeof ref === 'function') {
ref({ open: handleOpen });
} else if (ref) {
ref.current = { open: handleOpen };
}
if (renderTrigger === null) {
return null;
}
return renderTrigger ? <>{renderTrigger({ open: handleOpen })}</> : <button onClick={handleOpen}>remove</button>;
}),
}));
jest.mock('@/components/waves/specs/groups/group/edit/WaveGroupRemoveButton', () => {
const React = require('react');
return {
__esModule: true,
default: React.forwardRef(({ onWaveUpdate, renderTrigger }: any, ref) => {
const handleOpen = () => onWaveUpdate({});
React.useImperativeHandle(ref, () => ({ open: handleOpen }), [handleOpen]);
if (renderTrigger === null) {
return null;
}
return renderTrigger ? <>{renderTrigger({ open: handleOpen })}</> : <button onClick={handleOpen}>remove</button>;
}),
};
});

jest.mock('@/components/waves/specs/groups/group/edit/WaveGroupManageIdentitiesModal', () => ({
__esModule: true,
Expand All @@ -63,15 +64,17 @@ jest.mock('@/components/distribution-plan-tool/common/CircleLoader', () => ({
}));

const mutateAsync = jest.fn();
const queryClientMock = {
ensureQueryData: jest.fn(),
fetchQuery: jest.fn(),
const createQueryClientMock = () => ({
setQueryData: jest.fn(),
};
ensureQueryData: jest.fn().mockImplementation(async ({ queryFn }: any) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
}),
fetchQuery: jest.fn().mockImplementation(async ({ queryFn }: any) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
}),
});

(useMutation as jest.Mock).mockReturnValue({ mutateAsync });
(useQuery as jest.Mock).mockReturnValue({ data: undefined });
(useQueryClient as jest.Mock).mockImplementation(() => queryClientMock);
let queryClientMock = createQueryClientMock();

const auth = {
setToast: jest.fn(),
Expand Down Expand Up @@ -120,16 +123,16 @@ const wave: any = {
describe('WaveGroupEditButtons', () => {
beforeEach(() => {
jest.clearAllMocks();
mutateAsync.mockClear();
queryClientMock.ensureQueryData.mockReset();
queryClientMock.fetchQuery.mockReset();
queryClientMock.setQueryData.mockReset();
queryClientMock.ensureQueryData.mockResolvedValue(null);
queryClientMock.fetchQuery.mockResolvedValue([]);
queryClientMock.setQueryData.mockImplementation(() => {});
mutateAsync.mockReset();
queryClientMock = createQueryClientMock();
(useMutation as jest.Mock).mockReturnValue({ mutateAsync });
(useQuery as jest.Mock).mockReturnValue({ data: undefined });
(useQueryClient as jest.Mock).mockImplementation(() => queryClientMock);
(useQuery as jest.Mock).mockImplementation(({ enabled, queryFn }) => {
if (enabled && typeof queryFn === 'function') {
void queryFn({ signal: undefined });
}
return { data: undefined };
});
(useQueryClient as jest.Mock).mockReturnValue(queryClientMock);
});

it('opens menu and calls mutate on edit', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ import {
publishGroup as publishGroupMutation,
} from '@/services/groups/groupMutations';

jest.mock('@tanstack/react-query', () => ({
useMutation: jest.fn(),
useQuery: jest.fn(),
useQueryClient: jest.fn(),
}));
jest.mock('@tanstack/react-query', () => {
const actual = jest.requireActual('@tanstack/react-query');
return {
...actual,
useMutation: jest.fn(),
useQuery: jest.fn(),
useQueryClient: jest.fn(),
};
});

const mockCommonApiFetch = jest.fn();
const mockCommonApiPost = jest.fn();
Expand All @@ -36,11 +40,16 @@ jest.mock('@/services/groups/groupMutations', () => {

const mutateAsyncSpy = jest.fn();

const queryClientMock = {
ensureQueryData: jest.fn(),
fetchQuery: jest.fn(),
const createQueryClientMock = () => ({
setQueryData: jest.fn(),
};
ensureQueryData: jest.fn().mockImplementation(async ({ queryFn }: any) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
}),
fetchQuery: jest.fn().mockImplementation(async ({ queryFn }: any) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
}),
});
let queryClientMock = createQueryClientMock();

const mockCreateGroup = createGroupMutation as jest.Mock;
const mockPublishGroup = publishGroupMutation as jest.Mock;
Expand Down Expand Up @@ -113,19 +122,10 @@ const onWaveCreated = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mutateAsyncSpy.mockClear();
queryClientMock.ensureQueryData.mockReset();
queryClientMock.fetchQuery.mockReset();
queryClientMock.setQueryData.mockReset();
queryClientMock.ensureQueryData.mockImplementation(async ({ queryFn }) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
});
queryClientMock.fetchQuery.mockImplementation(async ({ queryFn }) => {
return queryFn ? await queryFn({ signal: undefined }) : undefined;
});
queryClientMock.setQueryData.mockImplementation(() => {});
(useQueryClient as jest.Mock).mockImplementation(() => queryClientMock);
queryClientMock = createQueryClientMock();
(useQueryClient as jest.Mock).mockReturnValue(queryClientMock);
(useQuery as jest.Mock).mockImplementation(({ enabled, queryFn }) => {
if (enabled && typeof queryFn === "function") {
if (enabled && typeof queryFn === 'function') {
void queryFn({ signal: undefined });
}
return { data: undefined };
Expand All @@ -146,6 +146,11 @@ beforeEach(() => {
}
},
}));
mockCommonApiFetch.mockReset();
mockCommonApiPost.mockReset();
mockCreateGroup.mockReset();
mockPublishGroup.mockReset();
requestAuth.mockResolvedValue({ success: true });
mockCommonApiPost.mockResolvedValue({});
mockCreateGroup.mockResolvedValue({
...baseGroupFull,
Expand Down Expand Up @@ -256,9 +261,18 @@ describe('useWaveGroupEditButtonsController - identity management', () => {
oldVersionId: null,
}),
);
expect(mockCommonApiPost).toHaveBeenCalled();
expect(mutateAsyncSpy).toHaveBeenCalled();
expect(onWaveCreated).toHaveBeenCalled();
expect(mockCommonApiPost).toHaveBeenCalledWith(
expect.objectContaining({
endpoint: 'waves/wave-1',
body: expect.objectContaining({
visibility: expect.objectContaining({
scope: expect.objectContaining({ group_id: 'new-group-id' }),
}),
}),
}),
);
expect(mutateAsyncSpy).toHaveBeenCalledTimes(1);
expect(onWaveCreated).toHaveBeenCalledTimes(1);
expect(setToast).toHaveBeenCalledWith({
message: 'Identity successfully included in the group.',
type: 'success',
Expand Down
1 change: 1 addition & 0 deletions codex/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This table is the single source of truth for active and historical tickets. Keep
| TKT-0013 | Respect unstyled flag in compact menu button | In-Progress | P1 | openai-assistant | — | 2025-10-23 |
| TKT-0014 | Replace wave publish wait with backend confirmation | Backlog | P1 | openai-assistant | — | 2025-10-24 |
| TKT-0015 | Unify header search results | In-Progress | P1 | openai-assistant | [#1567](https://github.com/6529-Collections/6529seize-frontend/pull/1567) | 2025-10-24 |
| TKT-0016 | Upgrade Next.js app to version 16 | In-Progress | P0 | openai-assistant | — | 2025-10-27 |

## Usage Guidelines

Expand Down
1 change: 1 addition & 0 deletions codex/tickets/TKT-0012.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ title: Refactor wave group edit buttons for modular clarity
- 2025-10-26T13:45:00Z – Rebuilt the visual profile search option as a button to resolve Sonar rule S6842 without altering the picker layout.
- 2025-10-26T16:02:20Z – Enabled quick include/exclude identity actions without pre-existing groups, aligned naming, and routed wave updates through the shared auth helper to keep the controller logic consistent.
- 2025-10-26T16:30:00Z – Removed an inline comment from the controller hook to comply with the repository's TypeScript comment ban.
- 2025-10-28T10:17:42Z – Resolved merge conflicts across the wave group edit and identity search test suites, restoring Jest, lint, and type safety checks.
37 changes: 37 additions & 0 deletions codex/tickets/TKT-0016.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
created: 2025-10-27
id: TKT-0016
owner: openai-assistant
priority: P0
status: In-Progress
title: Upgrade Next.js app to version 16
---

## Context

> Upgrade the frontend to Next.js 16 using the provided Next Devtools automation so we can adopt the latest React 19 runtime and platform tooling.

## Plan

- [x] Run the Next.js 16 upgrade automation and review generated diffs.
- [x] Address any manual follow-ups required by the codemod.
- [x] Validate the application by running tests, lint, and type-check.

## Acceptance

- [x] `next` and related dependencies updated to version 16 and app boots locally.
- [x] `npm run test`, `npm run lint`, and `npm run type-check` pass without errors.
- [x] Documented the upgrade outcome and any required follow-ups.

## Links

- Primary PR: [Next.js 16 upgrade](https://github.com/6529-Collections/6529seize-frontend/pull/1570)
- Follow-ups: _(reference additional tickets or TODO items)_

## Log

- 2025-10-27T12:46:40Z – Ticket opened to track the Next.js 16 upgrade.
- 2025-10-27T12:58:02Z – Ran the official Next.js codemod (upgrade latest); dependencies bumped and middleware renamed to proxy.tsx pending follow-up checks.
- 2025-10-27T13:19:35Z – Completed lint config migration and scripts updates; lint/type-check green but `npm run test` now fails across multiple suites under React 19 assertions (e.g., stricter accessibility expectations).
- 2025-10-28T05:33:50Z – Re-ran `npm run test`; 7 suites/14 tests still failing (e.g., `CommonProfileSearchItem` alt text expectation). `npm run lint` and `npm run type-check` succeed with warnings only.
- 2025-10-28T06:01:42Z – Addressed React 19/Headless UI test updates (profile search, dropdowns, wave group controllers, tab overflow); mocks now align with TanStack v5 APIs. `npm run test`, `npm run lint`, and `npm run type-check` all pass (lint warnings persist).
10 changes: 10 additions & 0 deletions config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ const raw =
const parsed = publicEnvSchema.parse(raw ? JSON.parse(raw) : {});

export const publicEnv: PublicEnv = parsed;

export const getNodeEnv = (): string | undefined => {
if (publicEnv.NODE_ENV) {
return publicEnv.NODE_ENV;
}
if (typeof process === "undefined") {
return undefined;
}
return process.env.NODE_ENV;
};
Loading