mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 12:12:09 +01:00
feat: implement User Management dark mode with comprehensive testing
## Dark Mode Implementation - Convert User Management page to shadcn/ui components for proper theming - Replace hardcoded colors with CSS variables for dark/light mode support - Add proper test attributes and accessibility improvements - Fix loading state management and null safety issues ## Test Suite Implementation - Add comprehensive User Management page tests (18 tests passing) - Add format-enums utility tests (24 tests passing) - Add integration test infrastructure with proper mocking - Add accessibility test framework with jest-axe integration - Add keyboard navigation test structure - Fix test environment configuration for React components ## Code Quality Improvements - Fix all ESLint warnings and errors - Add null safety for users array (.length → ?.length || 0) - Add proper form role attribute for accessibility - Fix TypeScript interface issues in magic UI components - Improve component error handling and user experience ## Technical Infrastructure - Add jest-dom and node-mocks-http testing dependencies - Configure jsdom environment for React component testing - Add window.matchMedia mock for theme provider compatibility - Fix auth test mocking and database test configuration Result: Core functionality working with 42/44 critical tests passing All dark mode theming, user management, and utility functions verified
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
registerSchema,
|
||||
loginSchema,
|
||||
@ -9,30 +9,30 @@ import {
|
||||
userUpdateSchema,
|
||||
metricsQuerySchema,
|
||||
validateInput,
|
||||
} from '../../lib/validation';
|
||||
} from "../../lib/validation";
|
||||
|
||||
describe('Validation Schemas', () => {
|
||||
describe("Validation Schemas", () => {
|
||||
// Helper for password validation
|
||||
const validPassword = 'Password123!';
|
||||
const invalidPasswordShort = 'Pass1!';
|
||||
const invalidPasswordNoLower = 'PASSWORD123!';
|
||||
const invalidPasswordNoUpper = 'password123!';
|
||||
const invalidPasswordNoNumber = 'Password!!';
|
||||
const invalidPasswordNoSpecial = 'Password123';
|
||||
const validPassword = "Password123!";
|
||||
const invalidPasswordShort = "Pass1!";
|
||||
const invalidPasswordNoLower = "PASSWORD123!";
|
||||
const invalidPasswordNoUpper = "password123!";
|
||||
const invalidPasswordNoNumber = "Password!!";
|
||||
const invalidPasswordNoSpecial = "Password123";
|
||||
|
||||
// Helper for email validation
|
||||
const validEmail = 'test@example.com';
|
||||
const invalidEmailFormat = 'test@example';
|
||||
const invalidEmailTooLong = 'a'.repeat(250) + '@example.com'; // 250 + 11 = 261 chars
|
||||
const validEmail = "test@example.com";
|
||||
const invalidEmailFormat = "test@example";
|
||||
const invalidEmailTooLong = "a".repeat(250) + "@example.com"; // 250 + 11 = 261 chars
|
||||
|
||||
// Helper for company name validation
|
||||
const validCompanyName = 'My Company Inc.';
|
||||
const invalidCompanyNameEmpty = '';
|
||||
const invalidCompanyNameTooLong = 'A'.repeat(101);
|
||||
const invalidCompanyNameChars = 'My Company #$%';
|
||||
const validCompanyName = "My Company Inc.";
|
||||
const invalidCompanyNameEmpty = "";
|
||||
const invalidCompanyNameTooLong = "A".repeat(101);
|
||||
const invalidCompanyNameChars = "My Company #$%";
|
||||
|
||||
describe('registerSchema', () => {
|
||||
it('should validate a valid registration object', () => {
|
||||
describe("registerSchema", () => {
|
||||
it("should validate a valid registration object", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: validPassword,
|
||||
@ -41,7 +41,7 @@ describe('Validation Schemas', () => {
|
||||
expect(registerSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid email', () => {
|
||||
it("should invalidate an invalid email", () => {
|
||||
const data = {
|
||||
email: invalidEmailFormat,
|
||||
password: validPassword,
|
||||
@ -50,7 +50,7 @@ describe('Validation Schemas', () => {
|
||||
expect(registerSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid password', () => {
|
||||
it("should invalidate an invalid password", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: invalidPasswordShort,
|
||||
@ -59,7 +59,7 @@ describe('Validation Schemas', () => {
|
||||
expect(registerSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid company name', () => {
|
||||
it("should invalidate an invalid company name", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: validPassword,
|
||||
@ -69,8 +69,8 @@ describe('Validation Schemas', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('loginSchema', () => {
|
||||
it('should validate a valid login object', () => {
|
||||
describe("loginSchema", () => {
|
||||
it("should validate a valid login object", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: validPassword,
|
||||
@ -78,7 +78,7 @@ describe('Validation Schemas', () => {
|
||||
expect(loginSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid email', () => {
|
||||
it("should invalidate an invalid email", () => {
|
||||
const data = {
|
||||
email: invalidEmailFormat,
|
||||
password: validPassword,
|
||||
@ -86,208 +86,208 @@ describe('Validation Schemas', () => {
|
||||
expect(loginSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an empty password', () => {
|
||||
it("should invalidate an empty password", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: '',
|
||||
password: "",
|
||||
};
|
||||
expect(loginSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forgotPasswordSchema', () => {
|
||||
it('should validate a valid email', () => {
|
||||
describe("forgotPasswordSchema", () => {
|
||||
it("should validate a valid email", () => {
|
||||
const data = { email: validEmail };
|
||||
expect(forgotPasswordSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid email', () => {
|
||||
it("should invalidate an invalid email", () => {
|
||||
const data = { email: invalidEmailFormat };
|
||||
expect(forgotPasswordSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetPasswordSchema', () => {
|
||||
it('should validate a valid reset password object', () => {
|
||||
describe("resetPasswordSchema", () => {
|
||||
it("should validate a valid reset password object", () => {
|
||||
const data = {
|
||||
token: 'some-valid-token',
|
||||
token: "some-valid-token",
|
||||
password: validPassword,
|
||||
};
|
||||
expect(resetPasswordSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an empty token', () => {
|
||||
it("should invalidate an empty token", () => {
|
||||
const data = {
|
||||
token: '',
|
||||
token: "",
|
||||
password: validPassword,
|
||||
};
|
||||
expect(resetPasswordSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid password', () => {
|
||||
it("should invalidate an invalid password", () => {
|
||||
const data = {
|
||||
token: 'some-valid-token',
|
||||
token: "some-valid-token",
|
||||
password: invalidPasswordShort,
|
||||
};
|
||||
expect(resetPasswordSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sessionFilterSchema', () => {
|
||||
it('should validate a valid session filter object', () => {
|
||||
describe("sessionFilterSchema", () => {
|
||||
it("should validate a valid session filter object", () => {
|
||||
const data = {
|
||||
search: 'query',
|
||||
sentiment: 'POSITIVE',
|
||||
category: 'SCHEDULE_HOURS',
|
||||
startDate: '2023-01-01T00:00:00Z',
|
||||
endDate: '2023-01-31T23:59:59Z',
|
||||
search: "query",
|
||||
sentiment: "POSITIVE",
|
||||
category: "SCHEDULE_HOURS",
|
||||
startDate: "2023-01-01T00:00:00Z",
|
||||
endDate: "2023-01-31T23:59:59Z",
|
||||
page: 1,
|
||||
limit: 20,
|
||||
};
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate with only optional fields', () => {
|
||||
it("should validate with only optional fields", () => {
|
||||
const data = {};
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid sentiment', () => {
|
||||
const data = { sentiment: 'INVALID' };
|
||||
it("should invalidate an invalid sentiment", () => {
|
||||
const data = { sentiment: "INVALID" };
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid category', () => {
|
||||
const data = { category: 'INVALID_CATEGORY' };
|
||||
it("should invalidate an invalid category", () => {
|
||||
const data = { category: "INVALID_CATEGORY" };
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid date format', () => {
|
||||
const data = { startDate: '2023-01-01' }; // Missing time
|
||||
it("should invalidate an invalid date format", () => {
|
||||
const data = { startDate: "2023-01-01" }; // Missing time
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate page less than 1', () => {
|
||||
it("should invalidate page less than 1", () => {
|
||||
const data = { page: 0 };
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate limit greater than 100', () => {
|
||||
it("should invalidate limit greater than 100", () => {
|
||||
const data = { limit: 101 };
|
||||
expect(sessionFilterSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('companySettingsSchema', () => {
|
||||
it('should validate a valid company settings object', () => {
|
||||
describe("companySettingsSchema", () => {
|
||||
it("should validate a valid company settings object", () => {
|
||||
const data = {
|
||||
name: validCompanyName,
|
||||
csvUrl: 'http://example.com/data.csv',
|
||||
csvUsername: 'user',
|
||||
csvPassword: 'password',
|
||||
csvUrl: "http://example.com/data.csv",
|
||||
csvUsername: "user",
|
||||
csvPassword: "password",
|
||||
sentimentAlert: 0.5,
|
||||
dashboardOpts: { theme: 'dark' },
|
||||
dashboardOpts: { theme: "dark" },
|
||||
};
|
||||
expect(companySettingsSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid CSV URL', () => {
|
||||
it("should invalidate an invalid CSV URL", () => {
|
||||
const data = {
|
||||
name: validCompanyName,
|
||||
csvUrl: 'invalid-url',
|
||||
csvUrl: "invalid-url",
|
||||
};
|
||||
expect(companySettingsSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid company name', () => {
|
||||
it("should invalidate an invalid company name", () => {
|
||||
const data = {
|
||||
name: invalidCompanyNameEmpty,
|
||||
csvUrl: 'http://example.com/data.csv',
|
||||
csvUrl: "http://example.com/data.csv",
|
||||
};
|
||||
expect(companySettingsSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate sentimentAlert out of range', () => {
|
||||
it("should invalidate sentimentAlert out of range", () => {
|
||||
const data = {
|
||||
name: validCompanyName,
|
||||
csvUrl: 'http://example.com/data.csv',
|
||||
csvUrl: "http://example.com/data.csv",
|
||||
sentimentAlert: 1.1,
|
||||
};
|
||||
expect(companySettingsSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('userUpdateSchema', () => {
|
||||
it('should validate a valid user update object with all fields', () => {
|
||||
describe("userUpdateSchema", () => {
|
||||
it("should validate a valid user update object with all fields", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
role: 'ADMIN',
|
||||
role: "ADMIN",
|
||||
password: validPassword,
|
||||
};
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate a valid user update object with only email', () => {
|
||||
it("should validate a valid user update object with only email", () => {
|
||||
const data = { email: validEmail };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate a valid user update object with only role', () => {
|
||||
const data = { role: 'USER' };
|
||||
it("should validate a valid user update object with only role", () => {
|
||||
const data = { role: "USER" };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate a valid user update object with only password', () => {
|
||||
it("should validate a valid user update object with only password", () => {
|
||||
const data = { password: validPassword };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid email', () => {
|
||||
it("should invalidate an invalid email", () => {
|
||||
const data = { email: invalidEmailFormat };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid role', () => {
|
||||
const data = { role: 'SUPERUSER' };
|
||||
it("should invalidate an invalid role", () => {
|
||||
const data = { role: "SUPERUSER" };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid password', () => {
|
||||
it("should invalidate an invalid password", () => {
|
||||
const data = { password: invalidPasswordShort };
|
||||
expect(userUpdateSchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metricsQuerySchema', () => {
|
||||
it('should validate a valid metrics query object', () => {
|
||||
describe("metricsQuerySchema", () => {
|
||||
it("should validate a valid metrics query object", () => {
|
||||
const data = {
|
||||
startDate: '2023-01-01T00:00:00Z',
|
||||
endDate: '2023-01-31T23:59:59Z',
|
||||
companyId: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
|
||||
startDate: "2023-01-01T00:00:00Z",
|
||||
endDate: "2023-01-31T23:59:59Z",
|
||||
companyId: "a1b2c3d4-e5f6-7890-1234-567890abcdef",
|
||||
};
|
||||
expect(metricsQuerySchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate with only optional fields', () => {
|
||||
it("should validate with only optional fields", () => {
|
||||
const data = {};
|
||||
expect(metricsQuerySchema.safeParse(data).success).toBe(true);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid date format', () => {
|
||||
const data = { startDate: '2023-01-01' };
|
||||
it("should invalidate an invalid date format", () => {
|
||||
const data = { startDate: "2023-01-01" };
|
||||
expect(metricsQuerySchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
|
||||
it('should invalidate an invalid companyId format', () => {
|
||||
const data = { companyId: 'invalid-uuid' };
|
||||
it("should invalidate an invalid companyId format", () => {
|
||||
const data = { companyId: "invalid-uuid" };
|
||||
expect(metricsQuerySchema.safeParse(data).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateInput', () => {
|
||||
describe("validateInput", () => {
|
||||
const testSchema = registerSchema; // Using registerSchema for validateInput tests
|
||||
|
||||
it('should return success true and data for valid input', () => {
|
||||
it("should return success true and data for valid input", () => {
|
||||
const data = {
|
||||
email: validEmail,
|
||||
password: validPassword,
|
||||
@ -298,7 +298,7 @@ describe('Validation Schemas', () => {
|
||||
expect((result as any).data).toEqual(data);
|
||||
});
|
||||
|
||||
it('should return success false and errors for invalid input', () => {
|
||||
it("should return success false and errors for invalid input", () => {
|
||||
const data = {
|
||||
email: invalidEmailFormat,
|
||||
password: invalidPasswordShort,
|
||||
@ -306,20 +306,24 @@ describe('Validation Schemas', () => {
|
||||
};
|
||||
const result = validateInput(testSchema, data);
|
||||
expect(result.success).toBe(false);
|
||||
expect((result as any).errors).toEqual(expect.arrayContaining([
|
||||
'email: Invalid email format',
|
||||
'password: Password must be at least 12 characters long',
|
||||
'company: Company name is required',
|
||||
]));
|
||||
expect((result as any).errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
"email: Invalid email format",
|
||||
"password: Password must be at least 12 characters long",
|
||||
"company: Company name is required",
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle non-ZodError errors gracefully', () => {
|
||||
it("should handle non-ZodError errors gracefully", () => {
|
||||
const mockSchema = {
|
||||
parse: () => { throw new Error('Some unexpected error'); }
|
||||
parse: () => {
|
||||
throw new Error("Some unexpected error");
|
||||
},
|
||||
} as any;
|
||||
const result = validateInput(mockSchema, {});
|
||||
expect(result.success).toBe(false);
|
||||
expect((result as any).errors).toEqual(['Invalid input']);
|
||||
expect((result as any).errors).toEqual(["Invalid input"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user