Skip to content

Commit

Permalink
feat: handle question component in usePageHasResponse (#1199)
Browse files Browse the repository at this point in the history
  • Loading branch information
chloe-renaud authored Feb 4, 2025
1 parent d389eae commit 33ac488
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 44 deletions.
1 change: 1 addition & 0 deletions src/components/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export type ComponentPropsByType = {
components: LunaticComponentProps[];
componentType?: 'Question';
iteration?: number;
value: Record<string, unknown>;
};
RosterForLoop: LunaticBaseProps<unknown> &
LunaticExtraProps & {
Expand Down
143 changes: 143 additions & 0 deletions src/use-lunatic/hooks/use-page-has-response.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { describe, expect, it, vi } from 'vitest';
import { usePageHasResponse } from './use-page-has-response';
import { type LunaticComponentProps } from '../../components/type';
import { renderHook } from '@testing-library/react';

const defaultComponentValues = {
id: 'a',
value: null,
handleChanges: vi.fn(),
};

describe('usePageHasResponse', () => {
it('should be true when there are no components', () => {
const components: LunaticComponentProps[] = [];
const { result } = renderHook(() =>
usePageHasResponse(components, vi.fn())
);
expect(result.current()).toBeTruthy();
});

it('should be false when there is no value', () => {
const components: LunaticComponentProps[] = [
{ ...defaultComponentValues, componentType: 'Text' },
];
const { result } = renderHook(() =>
usePageHasResponse(components, vi.fn())
);
expect(result.current()).toBeFalsy();
});

it('should be true when there is a value', () => {
const components: LunaticComponentProps[] = [
{
...defaultComponentValues,
componentType: 'Text',
value: 'my awesome value',
},
];
const { result } = renderHook(() =>
usePageHasResponse(components, vi.fn())
);
expect(result.current()).toBeTruthy();
});

it('should be true for a Question component', () => {
const components: LunaticComponentProps[] = [
{
...defaultComponentValues,
componentType: 'Question',
components: [
{
...defaultComponentValues,
componentType: 'Text',
},
],
value: { t1: 'my awesome value' },
},
];
const { result } = renderHook(() =>
usePageHasResponse(components, vi.fn())
);
expect(result.current()).toBeTruthy();
});

it('should be true when there is a missing response', () => {
const components: LunaticComponentProps[] = [
{
...defaultComponentValues,
componentType: 'Text',
missingResponse: { name: 'a_MISSING', value: 'my missing value' },
},
];
const { result } = renderHook(() =>
usePageHasResponse(components, vi.fn())
);
expect(result.current()).toBeTruthy();
});

it('should be true for a QCM table', () => {
const mockExecuteExpression = vi.fn();
mockExecuteExpression.mockReturnValueOnce('my value');
const components: LunaticComponentProps[] = [
{
...defaultComponentValues,
componentType: 'Table',
body: [
[
{
...defaultComponentValues,
id: 't1',
componentType: 'CheckboxBoolean',
response: { name: 't1' },
},
],
],
header: [{ label: 'my label' }],
executeExpression: vi.fn(),
iteration: 0,
value: {},
},
];
const { result } = renderHook(() =>
usePageHasResponse(components, mockExecuteExpression)
);
expect(result.current()).toBeTruthy();
});

it('should be true for a QCM table in a Question', () => {
const mockExecuteExpression = vi.fn();
mockExecuteExpression.mockReturnValueOnce('my value');
const components: LunaticComponentProps[] = [
{
...defaultComponentValues,
componentType: 'Question',
components: [
{
...defaultComponentValues,
componentType: 'Table',
body: [
[
{
...defaultComponentValues,
id: 't1',
componentType: 'CheckboxBoolean',
response: { name: 't1' },
},
],
],
header: [{ label: 'my label' }],
executeExpression: vi.fn(),
iteration: 0,
value: { t1: true },
},
],
value: {},
},
];
const { result } = renderHook(() =>
usePageHasResponse(components, mockExecuteExpression)
);
expect(result.current()).toBeTruthy();
});
});
105 changes: 61 additions & 44 deletions src/use-lunatic/hooks/use-page-has-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,76 @@ export function usePageHasResponse(
executeExpression: LunaticReducerState['executeExpression']
): () => boolean {
return useCallback(() => {
if (!Array.isArray(components) || components.length === 0) {
return hasOneResponse(components, executeExpression);
}, [components, executeExpression]);
}

function hasOneResponse(
components: LunaticComponentProps[],
executeExpression: LunaticReducerState['executeExpression']
): boolean {
if (!Array.isArray(components) || components.length === 0) {
return true;
}

for (const component of components) {
// Some components are considered as "filled" by default
// We assume they are not in the same page has other components
if (
['PairwiseLinks', 'Roundabout', 'Sequence', 'Subsequence'].includes(
component.componentType ?? ''
)
) {
return true;
}

for (const component of components) {
// Some components are considered as "filled" by default
// We assume they are not in the same page has other components
if (
['PairwiseLinks', 'Roundabout', 'Sequence', 'Subsequence'].includes(
component.componentType ?? ''
)
) {
return true;
}
// We have a missing response for this component
if (
'missingResponse' in component &&
component.missingResponse &&
component.missingResponse.value
) {
return true;
}

// We have a missing response for this component
if (
'missingResponse' in component &&
component.missingResponse &&
component.missingResponse.value
) {
return true;
}
// For Table, we have to extract components from its body and apply isSubComponentsEmpty function
if (component.componentType === 'Table') {
// Body is array for array (row), each "cell" could be an Label or Component, so we filter array.
const childrenComponent = component.body.reduce((_, row) => {
const componentsInRow = row.filter(
(cell) => isObject(cell) && 'componentType' in cell
);
return [..._, ...componentsInRow];
}, [] as LunaticComponentProps[]);
return !isSubComponentsEmpty(childrenComponent, executeExpression);
}

// For Table, we have to extract components from its body and apply isSubComponentsEmpty function
if (component.componentType === 'Table') {
// Body is array for array (row), each "cell" could be an Label or Component, so we filter array.
const childrenComponent = component.body.reduce((_, row) => {
const componentsInRow = row.filter(
(cell) => isObject(cell) && 'componentType' in cell
);
return [..._, ...componentsInRow];
}, [] as LunaticComponentProps[]);
return !isSubComponentsEmpty(childrenComponent, executeExpression);
}
// We found a value in one of the root component
if ('value' in component && !isEmpty(component.value)) {
return true;
}

// We found a value in one of the root component
if ('value' in component && !isEmpty(component.value)) {
return true;
}
// For Question, we need to check subcomponents
if (
component.componentType === 'Question' &&
'components' in component &&
Array.isArray(component.components) &&
hasOneResponse(component.components, executeExpression)
) {
return true;
}

// For rosterForLoop we need to inspect child components
if (
'components' in component &&
Array.isArray(component.components) &&
!isSubComponentsEmpty(component.components, executeExpression)
) {
return true;
}
// For rosterForLoop we need to inspect child components
if (
'components' in component &&
Array.isArray(component.components) &&
!isSubComponentsEmpty(component.components, executeExpression)
) {
return true;
}
}

return false;
}, [components, executeExpression]);
return false;
}

/**
Expand Down

0 comments on commit 33ac488

Please sign in to comment.