Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dfd7d8c
Updated workflows (#10)
Zaiidmo Jan 30, 2026
03a1452
docs(workflow): add Git Flow and npm version requirements (#11)
Ciscode-M Jan 31, 2026
9694780
docs: added different documentations
Zaiidmo Jan 31, 2026
05d92c9
1.0.9
Zaiidmo Jan 31, 2026
1088697
ops: updated publishing trigger
Zaiidmo Jan 31, 2026
a32dc79
Fix/verify email UI (#13)
a-elkhiraooui-ciscode Feb 2, 2026
b2e6c6a
v1.0.11
Zaiidmo Feb 2, 2026
b59c2e6
v1.0.11
Zaiidmo Feb 2, 2026
1be41bc
Unit tests (#17)
a-elkhiraooui-ciscode Feb 4, 2026
ff17360
Error handling (#18)
a-elkhiraooui-ciscode Feb 4, 2026
c8e5d3c
feat(auth): add dynamic signup fields and custom endpoints
SAAD-MOUMOU Apr 14, 2026
b9e29d8
refactor(ui): cleanup imports and formatting in auth pages
SAAD-MOUMOU Apr 14, 2026
fad2355
fix(auth): remove accessToken from bootstrap useEffect deps to preven…
SAAD-MOUMOU Apr 16, 2026
12563c4
fix: resolve merge conflicts with develop (scripts + vitest versions)
SAAD-MOUMOU Apr 17, 2026
f4f745c
fix: resolve lint errors, test failures and add eslint + prettier dev…
SAAD-MOUMOU Apr 17, 2026
0d0fb37
chore: bump version to 1.0.15
SAAD-MOUMOU Apr 17, 2026
2e96948
test: enhance coverage for AuthKit-UI + fix typecheck and test:cov sc…
SAAD-MOUMOU Apr 20, 2026
b7293e7
fix: replace deprecated JSX.Element with React.ReactElement in AuthPr…
SAAD-MOUMOU Apr 20, 2026
5805c32
Merge branch 'develop' into bugfix/fix-auth-bootstrap-bounce
saadmoumou Apr 20, 2026
4fb986a
fix: upgrade @types/react-dom to ^19 to fix npm ci lock file mismatch
SAAD-MOUMOU Apr 20, 2026
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
29 changes: 11 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
"test:cov": "vitest --coverage",
"test:cov": "vitest run --coverage",
"verify": "npm run lint && npm run typecheck && npm run test:cov",
"prepublishOnly": "npm run verify && npm run build"
},
Expand All @@ -53,8 +53,8 @@
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^22.13.1",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.3.6",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^2.1.8",
"autoprefixer": "^10.4.20",
Expand Down
4 changes: 2 additions & 2 deletions src/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface Props {
}

/* ---------- tiny in-file route guard ----------------------- */
const RequireAuth: React.FC<{ children: JSX.Element }> = ({ children }) => {
const RequireAuth: React.FC<{ children: React.ReactElement }> = ({ children }) => {
const { isAuthenticated } = useAuthState();
const location = useLocation();
return isAuthenticated
Expand Down Expand Up @@ -215,7 +215,7 @@ export const AuthProvider: React.FC<Props> = ({ config, children }) => {
{/* everything else protected */}
<Route
path="*"
element={<RequireAuth>{children as JSX.Element}</RequireAuth>}
element={<RequireAuth>{children as React.ReactElement}</RequireAuth>}
/>
</Routes>

Expand Down
157 changes: 157 additions & 0 deletions tests/components/ProfilePage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { AuthStateCtx } from '../../src/context/AuthStateContext';
import { ProfilePage } from '../../src/components/ProfilePage';

const mockUser = {
id: 'u1',
email: 'user@example.com',
name: 'John Doe',
roles: ['admin'],
modules: ['menus'],
tenantId: 't1',
};

const mockApi = {
get: vi.fn().mockResolvedValue({
data: {
data: {
email: 'user@example.com',
fullname: { fname: 'John', lname: 'Doe' },
username: 'johndoe',
},
},
}),
patch: vi.fn().mockResolvedValue({ data: {} }),
interceptors: {
request: { use: vi.fn(), eject: vi.fn() },
response: { use: vi.fn(), eject: vi.fn() },
},
} as any;

const mockSetUser = vi.fn();

function renderProfile(user = mockUser, apiOverride = mockApi) {
return render(
<AuthStateCtx.Provider
value={{
isAuthenticated: true,
user,
accessToken: 'token',
api: apiOverride,
login: vi.fn(),
logout: vi.fn(),
setUser: mockSetUser,
}}
>
<ProfilePage />
</AuthStateCtx.Provider>
);
}

describe('ProfilePage', () => {
beforeEach(() => {
mockApi.get.mockReset();
mockApi.patch.mockReset();
mockSetUser.mockReset();
mockApi.get.mockResolvedValue({
data: {
data: {
email: 'user@example.com',
fullname: { fname: 'John', lname: 'Doe' },
username: 'johndoe',
},
},
});
mockApi.patch.mockResolvedValue({ data: {} });
});

it('renders "No user data available" when user is null', () => {
render(
<AuthStateCtx.Provider
value={{
isAuthenticated: false,
user: null,
accessToken: null,
api: mockApi,
login: vi.fn(),
logout: vi.fn(),
setUser: vi.fn(),
}}
>
<ProfilePage />
</AuthStateCtx.Provider>
);
expect(screen.getByText(/No user data available/i)).toBeInTheDocument();
});

it('loads and displays profile data on mount', async () => {
renderProfile();
await waitFor(() => {
// Avatar initial should be computed from loaded name
expect(screen.getByText('J')).toBeInTheDocument();
});
});

it('handles API error gracefully on load', async () => {
const errApi = {
...mockApi,
get: vi.fn().mockRejectedValue(new Error('network error')),
} as any;

Check warning on line 101 in tests/components/ProfilePage.test.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=CISCODE-MA_AuthKit-UI&issues=AZ2qslJbjZMKPPYp4cWj&open=AZ2qslJbjZMKPPYp4cWj&pullRequest=22
// Should not throw
renderProfile(mockUser, errApi);
await waitFor(() => {
// page still renders without crashing
expect(document.body).toBeDefined();
});
});

it('enters edit mode when Edit button is clicked', async () => {
renderProfile();
await waitFor(() => screen.getByTitle('Edit profile'));
fireEvent.click(screen.getByTitle('Edit profile'));
// After clicking Edit, save/cancel buttons should appear
await waitFor(() => expect(screen.getByText('Save changes')).toBeInTheDocument());
});

it('saves profile and shows success toast', async () => {
renderProfile();
await waitFor(() => screen.getByTitle('Edit profile'));
fireEvent.click(screen.getByTitle('Edit profile'));

await waitFor(() => screen.getByText('Save changes'));
fireEvent.click(screen.getByText('Save changes'));

await waitFor(() => {
expect(mockApi.patch).toHaveBeenCalledWith('/api/auth/me', expect.any(Object));
});
// Toast should appear with success
await waitFor(() => expect(screen.getByRole('status')).toBeInTheDocument());
});

it('shows error toast when save fails', async () => {
mockApi.patch.mockRejectedValueOnce(new Error('save error'));
renderProfile();
await waitFor(() => screen.getByTitle('Edit profile'));
fireEvent.click(screen.getByTitle('Edit profile'));

await waitFor(() => screen.getByText('Save changes'));
fireEvent.click(screen.getByText('Save changes'));

await waitFor(() => expect(screen.getByRole('status')).toBeInTheDocument());
expect(screen.getByText(/Save failed/i)).toBeInTheDocument();
});

it('cancels editing and reverts to original values', async () => {
renderProfile();
await waitFor(() => screen.getByTitle('Edit profile'));
fireEvent.click(screen.getByTitle('Edit profile'));

await waitFor(() => screen.getByText('Cancel'));
fireEvent.click(screen.getByText('Cancel'));

// Should be back to view mode (Edit profile button visible again)
await waitFor(() => expect(screen.getByTitle('Edit profile')).toBeInTheDocument());
});
});
26 changes: 26 additions & 0 deletions tests/components/SocialButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { SocialButton } from '../../src/components/actions/SocialButton';

describe('SocialButton', () => {
it('renders an image with the given icon src', () => {
render(<SocialButton icon="https://example.com/icon.svg" label="Google" />);
const img = screen.getByRole('img');
expect(img).toHaveAttribute('src', 'https://example.com/icon.svg');
expect(img).toHaveAttribute('alt', 'Google');
});

it('renders the label text when provided', () => {
render(<SocialButton icon="icon.svg" label="Sign in with Google" />);
expect(screen.getByText('Sign in with Google')).toBeInTheDocument();
});

it('hides the label when no label is provided', () => {
render(<SocialButton icon="icon.svg" />);
// img with empty alt is decorative (no role); find via querySelector
const img = document.querySelector('img')!;
expect(img).toHaveAttribute('alt', '');
expect(img).toHaveAttribute('src', 'icon.svg');
});
});
37 changes: 37 additions & 0 deletions tests/exports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Smoke test: importing from the package entry point covers
* src/index.ts and src/main/app.ts re-export lines.
*/
import { describe, it, expect } from 'vitest';

import {
AuthProvider,
useAuthState,
useHasRole,
useHasModule,
useCan,
RequirePermissions,
RbacContext,
RbacProvider,
useGrant,
} from '../src/main/app';

import { ProfilePage } from '../src/components/ProfilePage';

describe('package exports', () => {
it('exports all expected symbols from main/app', () => {
expect(AuthProvider).toBeDefined();
expect(useAuthState).toBeDefined();
expect(useHasRole).toBeDefined();
expect(useHasModule).toBeDefined();
expect(useCan).toBeDefined();
expect(RequirePermissions).toBeDefined();
expect(RbacContext).toBeDefined();
expect(RbacProvider).toBeDefined();
expect(useGrant).toBeDefined();
});

it('exports ProfilePage from components', () => {
expect(ProfilePage).toBeDefined();
});
});
Loading
Loading