Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spawner Profile page #135

Merged
merged 12 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion jhub_apps/static/css/index.css

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions jhub_apps/static/js/index.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import { Route, Routes } from 'react-router';
import { useRecoilState } from 'recoil';
import { Home } from './pages/home/home';
import { ServerTypes } from './pages/server-types/server-types';
import { currentJhData } from './store';
import { JhData } from './types/jupyterhub';
import { getJhData } from './utils/jupyterhub';
Expand All @@ -28,6 +29,7 @@ export const App = (): React.ReactElement => {
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/" element={<Home />} />
<Route path="/server-types" element={<ServerTypes />} />
</Routes>
</main>
</div>
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as FileInput } from './file-input/file-input';
export { default as FormGroup } from './form-group/form-group';
export { default as Label } from './label/label';
export { default as Modal } from './modal/modal';
export { default as RadioButton } from './radio-button/radio-button';
export { default as Select } from './select/select';
export type { SelectOption } from './select/select';
export { default as Tag } from './tag/tag';
Expand Down
44 changes: 44 additions & 0 deletions ui/src/components/radio-button/radio-button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import '@testing-library/jest-dom';
import { fireEvent, render } from '@testing-library/react';
import RadioButton, { RadioButtonProps } from './radio-button';

describe('RadioButton', () => {
const defaultProps: RadioButtonProps = {
id: 'test-id',
name: 'test-name',
label: 'Test Label',
value: 'test-value',
checked: false,
onChange: jest.fn(),
};

test('renders correctly', () => {
const { getByLabelText } = render(<RadioButton {...defaultProps} />);
expect(getByLabelText('Test Label')).toBeInTheDocument();
});

test('has class "radio-tile" when isTile is true', () => {
const props = { ...defaultProps, isTile: true };
const { container } = render(<RadioButton {...props} />);
expect(container.querySelector('.radio-tile')).toBeInTheDocument();
});

test('has class "radio-button" when isTile is false or not provided', () => {
const { container } = render(<RadioButton {...defaultProps} />);
expect(container.querySelector('.radio-button')).toBeInTheDocument();
});

test('calls onChange when clicked', () => {
const { getByLabelText } = render(<RadioButton {...defaultProps} />);
fireEvent.click(getByLabelText('Test Label'));
expect(defaultProps.onChange).toHaveBeenCalled();
});

test('calls onClick when provided and clicked', () => {
const onClick = jest.fn();
const props = { ...defaultProps, onClick };
const { getByLabelText } = render(<RadioButton {...props} />);
fireEvent.click(getByLabelText('Test Label'));
expect(onClick).toHaveBeenCalled();
});
});
77 changes: 77 additions & 0 deletions ui/src/components/radio-button/radio-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { ChangeEventHandler, MouseEventHandler } from 'react';

export interface RadioButtonProps {
/**
* The unique identifier for this component
*/
id: string;
/**
* The name for the radioButton input field string
*/
name: string;
/**
* Whether the component is rendered as a tile boolean
*/
isTile?: boolean;
/**
* Event handler will be triggered when the radioButton value changes
* changeEventHandler<HTMLInputElement>
*/
onChange: ChangeEventHandler<HTMLInputElement>;
/**
* Event handler will be triggered when the radioButton is clicked
* MouseEventHandler<HTMLInputElement>
*/
onClick?: MouseEventHandler<HTMLInputElement>;
/**
* The text inside of the radioButton string
*/
label: string;
/**
* The text below the label
*/
subtext?: string;
/**
* Default value of the radioButton string | number | readonly string[]
*/
value: string | number | readonly string[];
/**
* Whether the radioButton is checked by default boolean
*/
checked: boolean;
}
const RadioButton: React.FC<RadioButtonProps> = ({
id,
name,
isTile,
onChange,
onClick,
label,
subtext,
value,
checked,
}) => {
// Determine the class for styling based on `isTile` prop
const className = isTile ? 'radio-tile' : 'radio-button';
kildre marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className="radio-button">
<label htmlFor={id} className={className}>
<input
className="radio"
type="radio"
id={id}
name={name}
value={value}
checked={checked}
onChange={onChange}
onClick={onClick}
/>
{label}
<div className="subtext">{subtext}</div>
</label>
</div>
);
};

export default RadioButton;
86 changes: 86 additions & 0 deletions ui/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,92 @@
@tailwind components;
@tailwind utilities;

h1 {
font-size: 1.5rem;
line-height: 3rem;
font-weight: 700;
margin-top: 0;
margin-bottom: 1rem;
}

/* Basic link style */
a {
color: #0645ad; /* A shade of blue for the link */
text-decoration: none; /* Removes underline from links */
transition: color 0.3s ease-in-out; /* Smooth color transition */
text-decoration: underline;
}

/* Hover state */
a:hover {
color: #0a58ca; /* A darker shade of blue for hover state */
}

/* Active state */
a:active {
color: #03396c; /* An even darker shade for active/clicked state */
}

/* Visited state */
a:visited {
color: #4b2e83; /* A purple color for visited links */
}
aktech marked this conversation as resolved.
Show resolved Hide resolved

/* Utility classes */

.mb-0 {
margin-bottom: 0;
}

.my-2 {
margin: 2rem 0;
}

.mr-1 {
margin-right: 1rem;
}

.bt {
border-top: 1px solid #ccc;
}

.br-5 {
border-radius: 5px;
}

/* Radio buttons */
.radio-button {
display: block;
margin-bottom: 20px;
font-size: 1rem;
}
.radio-button label {
font-weight: bold;
}
.radio-button input[type='radio'] {
margin-right: 10px;
height: 1rem;
width: 1rem;
}
.radio-button .subtext {
font-size: 14px;
font-weight: normal;
margin-left: 1.6rem;
}

.button-container {
display: flex;
justify-content: space-between;
margin: 2rem 0;
padding: 2rem 0;
}
.button-container button {
}
Copy link
Collaborator

@jbouder jbouder Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed if not being used


.button-group {
display: flex;
}
aktech marked this conversation as resolved.
Show resolved Hide resolved

@layer components {
.container {
margin-left: auto;
Expand Down
1 change: 1 addition & 0 deletions ui/src/pages/home/app-form/app-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ export const AppForm = ({
) : (
<></>
)}

{currentFramework === 'custom' ? (
<FormGroup
errors={
Expand Down
65 changes: 65 additions & 0 deletions ui/src/pages/server-types/server-types.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import RadioButton from '../../components/radio-button/radio-button';
import { ServerTypes } from './server-types';

// Mock the RadioButton component
jest.mock('../../components/radio-button/radio-button', () => {
return {
__esModule: true,
default: jest.fn(),
};
});

describe('ServerTypes page', () => {
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
(RadioButton as jest.Mock).mockClear();

// Setup mock for RadioButton with a functional component that simulates the real one
(RadioButton as jest.Mock).mockImplementation(
({ id, name, subtext, checked, onChange }) => (
<label>
<input
type="radio"
value={id}
checked={checked}
onChange={onChange}
/>
{name} - {subtext}
</label>
),
);
});

test('renders the correct number of radio buttons', () => {
render(<ServerTypes />);
expect(screen.getAllByRole('radio').length).toBe(8);
});

test('selects a radio button and updates the state correctly', () => {
render(<ServerTypes />);

const firstRadioButton = screen.getAllByRole('radio')[0];
fireEvent.click(firstRadioButton);

expect(firstRadioButton).toBeChecked();
});

test('submits the selected server type', async () => {
render(<ServerTypes />);

const createAppButton = screen.getByText('Create App');
const firstRadioButton = screen.getAllByRole('radio')[0];

// Mocking console.log to test if the correct value is logged
const consoleSpy = jest.spyOn(console, 'log');

fireEvent.click(firstRadioButton);
fireEvent.click(createAppButton);

await waitFor(() =>
expect(consoleSpy).toHaveBeenCalledWith('Selected server type:', '1'),
);
});
});
Loading
Loading