1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

feat(podman): support add podman envs in the wizard [r8s-20] (#12056)
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled

This commit is contained in:
Ali 2024-09-25 11:55:07 +12:00 committed by GitHub
parent db616bc8a5
commit 32e94d4e4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
108 changed files with 1921 additions and 272 deletions

View file

@ -46,7 +46,7 @@ export function Tooltip({
position={position}
className={className}
>
<HelpCircle className="lucide" aria-hidden="true" />
<HelpCircle className="lucide" />
</TooltipWithChildren>
</span>
);

View file

@ -25,7 +25,7 @@ export function CopyButton({
fadeDelay = 1000,
displayText = 'copied',
className,
color,
color = 'default',
indicatorPosition = 'right',
children,
'data-cy': dataCy,
@ -52,7 +52,7 @@ export function CopyButton({
<div className={styles.container}>
{indicatorPosition === 'left' && copiedIndicator()}
<Button
className={className}
className={clsx(className, '!ml-0')}
color={color}
size="small"
onClick={handleCopy}

View file

@ -0,0 +1,302 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import {
createColumnHelper,
createTable,
getCoreRowModel,
} from '@tanstack/react-table';
import { Datatable, defaultGlobalFilterFn, Props } from './Datatable';
import {
BasicTableSettings,
createPersistedStore,
refreshableSettings,
RefreshableTableSettings,
} from './types';
import { useTableState } from './useTableState';
// Mock data and dependencies
type MockData = { id: string; name: string; age: number };
const mockData = [
{ id: '1', name: 'John Doe', age: 30 },
{ id: '2', name: 'Jane Smith', age: 25 },
{ id: '3', name: 'Bob Johnson', age: 35 },
];
const mockColumns = [
{ accessorKey: 'name', header: 'Name' },
{ accessorKey: 'age', header: 'Age' },
];
// mock table settings / state
export interface TableSettings
extends BasicTableSettings,
RefreshableTableSettings {}
function createStore(storageKey: string) {
return createPersistedStore<TableSettings>(storageKey, 'name', (set) => ({
...refreshableSettings(set),
}));
}
const storageKey = 'test-table';
const settingsStore = createStore(storageKey);
const mockSettingsManager = {
pageSize: 10,
search: '',
sortBy: undefined,
setSearch: vitest.fn(),
setSortBy: vitest.fn(),
setPageSize: vitest.fn(),
};
function DatatableWithStore(props: Omit<Props<MockData>, 'settingsManager'>) {
const tableState = useTableState(settingsStore, storageKey);
return (
<Datatable {...props} settingsManager={tableState} data-cy="test-table" />
);
}
describe('Datatable', () => {
it('renders the table with correct data', () => {
render(
<DatatableWithStore
dataset={mockData}
columns={mockColumns}
data-cy="test-table"
/>
);
expect(screen.getByText('John Doe')).toBeInTheDocument();
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
});
it('renders the table with a title', () => {
render(
<DatatableWithStore
dataset={mockData}
columns={mockColumns}
title="Test Table"
data-cy="test-table"
/>
);
expect(screen.getByText('Test Table')).toBeInTheDocument();
});
it('handles row selection when not disabled', () => {
render(
<DatatableWithStore
dataset={mockData}
columns={mockColumns}
data-cy="test-table"
/>
);
const checkboxes = screen.getAllByRole('checkbox');
fireEvent.click(checkboxes[1]); // Select the first row
// Check if the row is selected (you might need to adapt this based on your implementation)
expect(checkboxes[1]).toBeChecked();
});
it('disables row selection when disableSelect is true', () => {
render(
<DatatableWithStore
dataset={mockData}
columns={mockColumns}
disableSelect
data-cy="test-table"
/>
);
const checkboxes = screen.queryAllByRole('checkbox');
expect(checkboxes.length).toBe(0);
});
it('handles sorting', () => {
render(
<Datatable
dataset={mockData}
columns={mockColumns}
settingsManager={mockSettingsManager}
data-cy="test-table"
/>
);
const nameHeader = screen.getByText('Name');
fireEvent.click(nameHeader);
// Check if setSortBy was called with the correct arguments
expect(mockSettingsManager.setSortBy).toHaveBeenCalledWith('name', true);
});
it('renders loading state', () => {
render(
<DatatableWithStore
dataset={mockData}
columns={mockColumns}
isLoading
data-cy="test-table"
/>
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('renders empty state', () => {
render(
<DatatableWithStore
dataset={[]}
columns={mockColumns}
emptyContentLabel="No data available"
data-cy="test-table"
/>
);
expect(screen.getByText('No data available')).toBeInTheDocument();
});
});
// Test the defaultGlobalFilterFn used in searches
type Person = {
id: string;
name: string;
age: number;
isEmployed: boolean;
tags?: string[];
city?: string;
family?: { sister: string; uncles?: string[] };
};
const data: Person[] = [
{
// searching primitives should be supported
id: '1',
name: 'Alice',
age: 30,
isEmployed: true,
// supporting arrays of primitives should be supported
tags: ['music', 'likes-pixar'],
// supporting objects of primitives should be supported (values only).
// but shouldn't be support nested objects / arrays
family: { sister: 'sophie', uncles: ['john', 'david'] },
},
];
const columnHelper = createColumnHelper<Person>();
const columns = [
columnHelper.accessor('name', {
id: 'name',
}),
columnHelper.accessor('isEmployed', {
id: 'isEmployed',
}),
columnHelper.accessor('age', {
id: 'age',
}),
columnHelper.accessor('tags', {
id: 'tags',
}),
columnHelper.accessor('family', {
id: 'family',
}),
];
const mockTable = createTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
state: {},
onStateChange() {},
renderFallbackValue: undefined,
getRowId: (row) => row.id,
});
const mockRow = mockTable.getRow('1');
describe('defaultGlobalFilterFn', () => {
it('should return true when filterValue is null', () => {
const result = defaultGlobalFilterFn(mockRow, 'Name', null);
expect(result).toBe(true);
});
it('should return true when filterValue.search is empty', () => {
const result = defaultGlobalFilterFn(mockRow, 'Name', {
search: '',
});
expect(result).toBe(true);
});
it('should filter string values correctly', () => {
expect(
defaultGlobalFilterFn(mockRow, 'name', {
search: 'hello',
})
).toBe(false);
expect(
defaultGlobalFilterFn(mockRow, 'name', {
search: 'ALICE',
})
).toBe(true);
expect(
defaultGlobalFilterFn(mockRow, 'name', {
search: 'Alice',
})
).toBe(true);
});
it('should filter number values correctly', () => {
expect(defaultGlobalFilterFn(mockRow, 'age', { search: '123' })).toBe(
false
);
expect(defaultGlobalFilterFn(mockRow, 'age', { search: '30' })).toBe(true);
expect(defaultGlobalFilterFn(mockRow, 'age', { search: '67' })).toBe(false);
});
it('should filter boolean values correctly', () => {
expect(
defaultGlobalFilterFn(mockRow, 'isEmployed', { search: 'true' })
).toBe(true);
expect(
defaultGlobalFilterFn(mockRow, 'isEmployed', { search: 'false' })
).toBe(false);
});
it('should filter object values correctly', () => {
expect(defaultGlobalFilterFn(mockRow, 'family', { search: 'sophie' })).toBe(
true
);
expect(defaultGlobalFilterFn(mockRow, 'family', { search: '30' })).toBe(
false
);
});
it('should filter array values correctly', () => {
expect(defaultGlobalFilterFn(mockRow, 'tags', { search: 'music' })).toBe(
true
);
expect(
defaultGlobalFilterFn(mockRow, 'tags', { search: 'Likes-Pixar' })
).toBe(true);
expect(defaultGlobalFilterFn(mockRow, 'tags', { search: 'grape' })).toBe(
false
);
expect(defaultGlobalFilterFn(mockRow, 'tags', { search: 'likes' })).toBe(
true
);
});
it('should handle complex nested structures', () => {
expect(defaultGlobalFilterFn(mockRow, 'family', { search: 'sophie' })).toBe(
true
);
expect(defaultGlobalFilterFn(mockRow, 'family', { search: 'mason' })).toBe(
false
);
});
it('should not filter non-primitive values within objects and arrays', () => {
expect(defaultGlobalFilterFn(mockRow, 'family', { search: 'john' })).toBe(
false
);
expect(defaultGlobalFilterFn(mockRow, 'family', { search: 'david' })).toBe(
false
);
});
});

View file

@ -272,6 +272,21 @@ export function defaultGlobalFilterFn<D, TFilter extends { search: string }>(
const filterValueLower = filterValue.search.toLowerCase();
if (typeof value === 'object') {
return Object.values(value).some((item) =>
filterPrimitive(item, filterValueLower)
);
}
if (Array.isArray(value)) {
return value.some((item) => filterPrimitive(item, filterValueLower));
}
return filterPrimitive(value, filterValueLower);
}
// only filter primitive values within objects and arrays, to avoid searching nested objects
function filterPrimitive(value: unknown, filterValueLower: string) {
if (
typeof value === 'string' ||
typeof value === 'number' ||
@ -279,13 +294,6 @@ export function defaultGlobalFilterFn<D, TFilter extends { search: string }>(
) {
return value.toString().toLowerCase().includes(filterValueLower);
}
if (Array.isArray(value)) {
return value.some((item) =>
item.toString().toLowerCase().includes(filterValueLower)
);
}
return false;
}