Skip to content

Commit

Permalink
Merge pull request #856 from Enterprise-CMCS/main
Browse files Browse the repository at this point in the history
Release to val
  • Loading branch information
asharonbaltazar authored Nov 20, 2024
2 parents 18231d6 + d7f5fce commit 0d7df23
Show file tree
Hide file tree
Showing 17 changed files with 592 additions and 318 deletions.
24 changes: 12 additions & 12 deletions lib/local-constructs/clamav-scanning/package-lock.json

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

2 changes: 1 addition & 1 deletion lib/local-constructs/clamav-scanning/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.679.0",
"@types/mime-types": "^2.1.4",
"file-type": "^19.4.1",
"file-type": "^19.6.0",
"mime-types": "^2.1.35",
"pino": "^9.4.0"
}
Expand Down
2 changes: 1 addition & 1 deletion lib/stacks/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class Email extends cdk.NestedStack {

alarm.addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(alarmTopic));

new CfnEventSourceMapping(this, "SinkEmailTrigger", {
new CfnEventSourceMapping(this, "SinkSESTrigger", {
batchSize: 1,
enabled: true,
selfManagedEventSource: {
Expand Down
36 changes: 36 additions & 0 deletions react-app/src/components/Inputs/button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { Button } from './button';

describe('Button Component', () => {
it('renders children correctly', () => {
render(<Button>Click Me</Button>);
expect(screen.getByText('Click Me')).toBeInTheDocument();
});

it('applies default variant and size classes', () => {
render(<Button>Click Me</Button>);
const button = screen.getByText('Click Me');
expect(button).toHaveClass('bg-primary text-slate-50');
});

it('applies the correct variant and size classes when props are set', () => {
render(<Button variant="destructive" size="lg">Delete</Button>);
const button = screen.getByText('Delete');
expect(button).toHaveClass('bg-destructive');
expect(button).toHaveClass('h-11 px-8');
});

it('shows a loading spinner when loading prop is true', () => {
const { container } = render(<Button loading>Loading</Button>);
const spinner = container.querySelector('.animate-spin');
expect(spinner).toBeInTheDocument();
});

it('disables the button when the disabled prop is true', () => {
render(<Button disabled>Disabled</Button>);
const button = screen.getByText('Disabled');
expect(button).toBeDisabled();
});
});

105 changes: 105 additions & 0 deletions react-app/src/components/Inputs/checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Checkbox, CheckboxGroup } from './checkbox';
import React from 'react';

describe('Checkbox Component', () => {
it('renders correctly with a label', () => {
render(<Checkbox label="Accept Terms" />);
expect(screen.getByLabelText('Accept Terms')).toBeInTheDocument();
});

it('renders correctly with a styledLabel', () => {
render(<Checkbox styledLabel={<span>Styled Label</span>} />);
expect(screen.getByText('Styled Label')).toBeInTheDocument();
});

it('displays description if provided', () => {
render(<Checkbox label="Terms" description="Please accept terms and conditions" />);
expect(screen.getByText('Please accept terms and conditions')).toBeInTheDocument();
});

it('applies custom class names', () => {
render(<Checkbox label="Terms" className="custom-class" />);
const checkbox = screen.getByLabelText('Terms');
expect(checkbox).toHaveClass('custom-class');
});

it('toggles checked state when clicked', () => {
const handleChange = vi.fn();
render(<Checkbox label="Terms" onCheckedChange={handleChange} />);
const checkbox = screen.getByLabelText('Terms');

// Simulate checking the checkbox
fireEvent.click(checkbox);
expect(handleChange).toHaveBeenCalledWith(true);

// Simulate unchecking the checkbox
fireEvent.click(checkbox);
expect(handleChange).toHaveBeenCalledWith(false);
});
});

describe('CheckboxGroup Component', () => {
it('renders all options correctly', () => {
const options = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
{ label: 'Option 3', value: 'opt3' },
];
render(<CheckboxGroup value={[]} onChange={() => {}} options={options} />);

options.forEach((opt) => {
expect(screen.getByLabelText(opt.label)).toBeInTheDocument();
});
});

it('checks the correct checkboxes based on initial value', () => {
const options = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
];
render(<CheckboxGroup value={['opt1']} onChange={() => {}} options={options} />);

expect(screen.getByLabelText('Option 1')).toBeChecked();
expect(screen.getByLabelText('Option 2')).not.toBeChecked();
});

it('calls onChange with the correct values when a checkbox is toggled', () => {
const options = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
];
const handleChange = vi.fn();

// State wrapper for CheckboxGroup to handle the "value" prop directly
const CheckboxGroupWrapper = () => {
const [value, setValue] = React.useState<string[]>([]);
return (
<CheckboxGroup
value={value}
onChange={(newValue) => {
setValue(newValue); // Update internal state
handleChange(newValue); // Call the spy
}}
options={options}
/>
);
};

// Render the wrapper component
render(<CheckboxGroupWrapper />);

// 1. Check "Option 1"
fireEvent.click(screen.getByLabelText('Option 1'));
expect(handleChange).toHaveBeenNthCalledWith(1, ['opt1']);

// 2. Log output and then Check "Option 2"
fireEvent.click(screen.getByLabelText('Option 2'));
expect(handleChange).toHaveBeenNthCalledWith(2, ['opt1', 'opt2']); // Expect both checked

// 3. Uncheck "Option 1"
fireEvent.click(screen.getByLabelText('Option 1'));
expect(handleChange).toHaveBeenNthCalledWith(3, ['opt2']);
});
});
23 changes: 23 additions & 0 deletions react-app/src/components/Inputs/switch.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Switch } from "./switch";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";

describe("Switch", () => {
it("should render", () => {
render(<Switch />);
expect(screen.getByRole("switch")).toBeInTheDocument();
});

it("should call onChange and onCheckedChange when state changes", () => {
const onChange = vi.fn();
const onCheckedChange = vi.fn();

render(<Switch onChange={onChange} onCheckedChange={onCheckedChange} />);

const switchElement = screen.getByRole("switch");
fireEvent.click(switchElement);

expect(onChange).toHaveBeenCalledTimes(1);
expect(onCheckedChange).toHaveBeenCalledTimes(1);
});
});
5 changes: 2 additions & 3 deletions react-app/src/components/Inputs/switch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/utils";
Expand All @@ -11,7 +10,7 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
className,
)}
{...props}
onCheckedChange={(value) => {
Expand All @@ -22,7 +21,7 @@ const Switch = React.forwardRef<
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
)}
/>
</SwitchPrimitives.Root>
Expand Down
120 changes: 120 additions & 0 deletions react-app/src/components/Inputs/upload.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Upload } from "./upload";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock global fetch
global.fetch = vi.fn();

// Mock AWS Amplify API
vi.mock("aws-amplify", () => ({
API: {
post: vi.fn(),
},
}));

const defaultProps = {
dataTestId: "upload-component",
files: [],
setFiles: vi.fn(),
setErrorMessage: vi.fn(),
};

describe("Upload", () => {
const testIdSuffix = "upload";

beforeEach(() => {
vi.clearAllMocks();
});

it("renders correctly with initial props", () => {
render(<Upload {...defaultProps} />);
expect(screen.getByTestId(`${defaultProps.dataTestId}-${testIdSuffix}`)).toBeInTheDocument();
expect(screen.queryByText("Uploading...")).not.toBeInTheDocument();
});

it("uploads files correctly", async () => {
render(<Upload {...defaultProps} />);

const dropzone = screen.getByRole("presentation");
const file = new File(["file contents"], "file.pdf", { type: "application/pdf" });

Object.defineProperty(dropzone, "files", {
value: [file],
writable: false,
});

fireEvent.drop(dropzone);

await waitFor(() => {
expect(defaultProps.setFiles).toHaveBeenCalledWith([
{
bucket: "hello",
key: "world",
filename: "file.pdf",
title: "file",
uploadDate: expect.any(Number), // Since it's a timestamp
},
]);
});
});

it("displays an error for unsupported file types", async () => {
render(<Upload {...defaultProps} />);

const dropzone = screen.getByRole("presentation");
const file = new File(["file contents"], "file.exe", { type: "application/x-msdownload" });

Object.defineProperty(dropzone, "files", {
value: [file],
writable: false,
});

fireEvent.drop(dropzone);

await waitFor(() => {
expect(
screen.getByText("Selected file(s) is too large or of a disallowed file type."),
).toBeInTheDocument();
});
});

it("does not display the dropzone when uploading", async () => {
render(<Upload {...defaultProps} />);

const dropzone = screen.getByTestId("upload-component-upload");
const file = new File(["file contents"], "file.pdf", { type: "application/pdf" });

Object.defineProperty(dropzone, "files", {
value: [file],
writable: false,
});

fireEvent.drop(dropzone);

await waitFor(() => {
expect(screen.getByTestId("upload-component-upload")).not.toBeVisible();
});
});

it("handles file removal on event", () => {
const mockSetFiles = vi.fn();
const files = [
{ filename: "file-1.txt" },
{ filename: "file-to-remove.txt" },
{ filename: "file-2.txt" },
];

// Render the component with necessary props
render(<Upload {...defaultProps} files={files} setFiles={mockSetFiles} />);

// Simulate the event (e.g., a click on the remove button)
const removeButton = screen.getByTestId("upload-component-remove-file-file-to-remove.txt"); // Ensure your component uses this testId
fireEvent.click(removeButton);

// Assert that setFiles was called with the updated files array
expect(mockSetFiles).toHaveBeenCalledWith([
{ filename: "file-1.txt" },
{ filename: "file-2.txt" },
]);
});
});
Loading

0 comments on commit 0d7df23

Please sign in to comment.