Skip to content

Commit

Permalink
Fix bugs in List component (#2585)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjosttveit authored Oct 17, 2024
1 parent 8ebed04 commit c830203
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 34 deletions.
34 changes: 33 additions & 1 deletion src/layout/List/ListComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { userEvent } from '@testing-library/user-event';

import { defaultDataTypeMock } from 'src/__mocks__/getLayoutSetsMock';
import { useDataModelBindings } from 'src/features/formData/useDataModelBindings';
import * as useDeviceWidths from 'src/hooks/useDeviceWidths';
import { ListComponent } from 'src/layout/List/ListComponent';
import { renderGenericComponentTest } from 'src/test/renderWithProviders';
import { useNodeItem } from 'src/utils/layout/useNodeItem';
Expand Down Expand Up @@ -125,6 +126,11 @@ const render = async ({ component, ...rest }: Partial<RenderGenericComponentTest
});

describe('ListComponent', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
});

it('should render rows that is sent in but not rows that is not sent in', async () => {
await render();

Expand Down Expand Up @@ -190,7 +196,33 @@ describe('ListComponent', () => {
{ op: 'add', path: '/CountryPopulation', value: 6 },
{ op: 'add', path: '/CountryHighestMountain', value: 170 },
]);
});

jest.useRealTimers();
it('should save all field values in when in mobile', async () => {
jest.useFakeTimers();
jest.spyOn(useDeviceWidths, 'useIsMobile').mockReturnValue(true);

const user = userEvent.setup({ delay: null });
const { formDataMethods } = await render({ component: { tableHeadersMobile: ['Name', 'FlagLink'] } });

// Make sure test is not broken by changing mobile-view implementation
expect(useDeviceWidths.useIsMobile).toHaveBeenCalled();

// There should be one radio for each country, but none of them should be checked
await waitFor(() => expect(screen.getAllByRole('radio')).toHaveLength(6));
expect(screen.queryByRole('radio', { checked: true })).not.toBeInTheDocument();

// Select the second row
const swedishRow = screen.getByRole('radio', { name: /sweden/i });
await user.click(swedishRow);

expect(formDataMethods.setMultiLeafValues).toHaveBeenCalledWith({
debounceTimeout: undefined,
changes: [
{ reference: { field: 'CountryName', dataType: defaultDataTypeMock }, newValue: 'Sweden' },
{ reference: { field: 'CountryPopulation', dataType: defaultDataTypeMock }, newValue: 10 },
{ reference: { field: 'CountryHighestMountain', dataType: defaultDataTypeMock }, newValue: 1738 },
],
});
});
});
29 changes: 11 additions & 18 deletions src/layout/List/ListComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,12 @@ export const ListComponent = ({ node }: IListProps) => {
const bindings = item.dataModelBindings ?? ({} as IDataModelBindingsForList);
const { formData, setValues } = useDataModelBindings(bindings);

const filteredHeaders = Object.fromEntries(Object.entries(tableHeaders).filter(([key]) => shouldIncludeColumn(key)));
const filteredRows: Row[] =
data?.listItems?.map((row) => {
const result = Object.fromEntries(Object.entries(row).filter(([key]) => shouldIncludeColumn(key)));
return result;
}) ?? [];
const tableHeadersToShowInMobile = Object.keys(tableHeaders).filter(
(key) => !tableHeadersMobile || tableHeadersMobile.includes(key),
);

const selectedRow =
filteredRows.find((row) => Object.keys(formData).every((key) => row[key] === formData[key])) ?? '';
data?.listItems.find((row) => Object.keys(formData).every((key) => row[key] === formData[key])) ?? '';

function handleRowSelect({ selectedValue }: { selectedValue: Row }) {
const next: Row = {};
Expand All @@ -75,10 +72,6 @@ export const ListComponent = ({ node }: IListProps) => {
setValues(next);
}

function shouldIncludeColumn(key: string): boolean {
return !isMobile || !tableHeadersMobile || tableHeadersMobile.includes(key);
}

function isRowSelected(row: Row): boolean {
return JSON.stringify(selectedRow) === JSON.stringify(row);
}
Expand All @@ -105,17 +98,17 @@ export const ListComponent = ({ node }: IListProps) => {
className={classes.mobileRadioGroup}
value={JSON.stringify(selectedRow)}
>
{filteredRows.map((row) => (
{data?.listItems.map((row) => (
<Radio
key={JSON.stringify(row)}
value={JSON.stringify(row)}
className={cn(classes.mobileRadio, { [classes.selectedRow]: isRowSelected(row) })}
onClick={() => handleRowSelect({ selectedValue: row })}
>
{Object.entries(row).map(([key, value]) => (
{tableHeadersToShowInMobile.map((key) => (
<div key={key}>
<strong>{tableHeaders[key]}</strong>
<span>{typeof value === 'string' ? lang(value) : value}</span>
<span>{typeof row[key] === 'string' ? lang(row[key]) : row[key]}</span>
</div>
))}
</Radio>
Expand Down Expand Up @@ -154,7 +147,7 @@ export const ListComponent = ({ node }: IListProps) => {
<Table.Head>
<Table.Row>
<Table.HeaderCell />
{Object.entries(filteredHeaders).map(([key, value]) => (
{Object.entries(tableHeaders).map(([key, value]) => (
<Table.HeaderCell
key={key}
sortable={sortableColumns?.includes(key)}
Expand All @@ -174,7 +167,7 @@ export const ListComponent = ({ node }: IListProps) => {
</Table.Row>
</Table.Head>
<Table.Body>
{filteredRows.map((row) => (
{data?.listItems.map((row) => (
<Table.Row
key={JSON.stringify(row)}
onClick={() => {
Expand All @@ -196,14 +189,14 @@ export const ListComponent = ({ node }: IListProps) => {
name={node.id}
/>
</Table.Cell>
{Object.entries(row).map(([key, value]) => (
{Object.keys(tableHeaders).map((key) => (
<Table.Cell
key={key}
className={cn({
[classes.selectedRowCell]: isRowSelected(row),
})}
>
{typeof value === 'string' ? lang(value) : value}
{typeof row[key] === 'string' ? lang(row[key]) : row[key]}
</Table.Cell>
))}
</Table.Row>
Expand Down
23 changes: 8 additions & 15 deletions src/layout/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,16 @@ export class List extends ListDef {
}

validateDataModelBindings(ctx: LayoutValidationCtx<'List'>): string[] {
const possibleBindings = Object.keys(ctx.item.tableHeaders ?? {});

const errors: string[] = [];

for (const binding of Object.keys(ctx.item.dataModelBindings ?? {})) {
if (possibleBindings.includes(binding)) {
const [newErrors] = this.validateDataModelBindingsAny(
ctx,
binding,
['string', 'number', 'integer', 'boolean'],
false,
);
errors.push(...(newErrors || []));
} else {
errors.push(
`Bindingen ${binding} er ikke gyldig for denne komponenten. Gyldige bindinger er definert i 'tableHeaders'`,
);
}
const [newErrors] = this.validateDataModelBindingsAny(
ctx,
binding,
['string', 'number', 'integer', 'boolean'],
false,
);
errors.push(...(newErrors || []));
}

return errors;
Expand Down

0 comments on commit c830203

Please sign in to comment.