diff --git a/src/components/form/fields/consentField.tsx b/src/components/form/fields/consentField.tsx index 5b7ffac3..05ae715c 100644 --- a/src/components/form/fields/consentField.tsx +++ b/src/components/form/fields/consentField.tsx @@ -2,13 +2,16 @@ import React from 'react'; import styled from 'styled-components'; +import { ConsentType } from '@reachfive/identity-core'; + import { Checkbox } from '../formControlsComponent'; import { createField, type FieldComponentProps, type FieldDefinition } from '../fieldCreator'; import { MarkdownContent } from '../../miscComponent'; +import { PathMapping } from '../../../core/mapping'; import { checked, empty, isValidatorError } from '../../../core/validation'; import { isRichFormValue } from '../../../helpers/utils'; -import { ConsentType } from '@reachfive/identity-core'; +import { snakeCasePath } from '../../../helpers/transformObjectProperties'; const Description = styled.div` font-size: ${props => props.theme.smallTextFontSize}px; @@ -80,6 +83,7 @@ export default function consentField({ ...props, required, defaultValue: { granted: props.defaultValue ?? false }, + mapping: new PathMapping(snakeCasePath(props.path ?? props.key)), // Consent key should be snake_case format: { bind: value => value, unbind: value => value !== undefined diff --git a/src/main.ts b/src/main.ts index af613305..6a3d4ebc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,14 +34,19 @@ export function createClient(creationConfig: Config): Client { return fetch(`https://${creationConfig.domain}/identity/v1/config/consents?${toQueryString({ lang: language })}`) .then(response => response.json()) - .then(consentsVersions => { + .then((consentsVersions: Record) => { return fetch(`${remoteConfig.resourceBaseUrl}/${language}.json`) .then(response => response.json()) .then(defaultI18n => { const config = { ...creationConfig, ...remoteConfig, - consentsVersions: camelCaseProperties(consentsVersions) as Record + consentsVersions: Object.fromEntries( + Object.entries(consentsVersions).map(([key, value]) => [ + key, // consents keys should be snake_case + camelCaseProperties(value) as ConsentVersions + ]) + ) } return new UiClient(config, coreClient, defaultI18n) }) diff --git a/src/widgets/profileEditor/profileEditorWidget.tsx b/src/widgets/profileEditor/profileEditorWidget.tsx index 585a44ec..74ff33f1 100644 --- a/src/widgets/profileEditor/profileEditorWidget.tsx +++ b/src/widgets/profileEditor/profileEditorWidget.tsx @@ -135,7 +135,23 @@ export default createWidget({ accessToken, fields: resolvedFields.map(({ path }) => path).join(',') }) - .then(profile => camelCaseProperties(profile) as Profile) /** @todo check api response key format in sdk core */ + /** + * @todo this can be removed when https://github.com/ReachFive/identity-web-core-sdk/pull/260 will be merged + * L'idéal serait que la clé d'un consentement soit en valeur d'une propriété "key" et non une clé de la structure Map utilisée. + * le mieux serait même que `consents` soit un array et non un map. + * Mais bon, difficile de changer ça maintenant sans créer un breaking change pour les utilisateurs de l'api ou du sdk core... + */ + .then(({ consents, ...profile }) => ({ + ...profile, + consents: consents + ? Object.fromEntries( + Object.entries(consents).map(([key, value]) => [ + key, + camelCaseProperties(value) as UserConsent + ]) + ) + : undefined + }) as Profile) /** @todo check api response key format in sdk core */ .then((profile: Profile) => { const filteredProfileConsents = (profile.consents && Object.keys(profile.consents).length > 0) ? filterProfileConsents(fields, config.consentsVersions, profile.consents) : undefined; const filteredOutConsentsProfile = { ...profile, consents: filteredProfileConsents }; @@ -156,7 +172,11 @@ export default createWidget({ }); // Filter out the profile consents with different version than the one the given consent field own -const filterProfileConsents = (fields: (string | Field)[], consentsVersions: Record, profileConsents: Record) => { +const filterProfileConsents = ( + fields: (string | Field)[], + consentsVersions: Record, + profileConsents: Record +) => { return Object.keys(profileConsents) .filter(profileConsentKey => { const consentField = fields.map(f => typeof f === 'string' ? f : f.key).find(field => field.startsWith(`consents.${profileConsentKey}`)); diff --git a/tests/widgets/profileEditor/profileEditorWidget.test.ts b/tests/widgets/profileEditor/profileEditorWidget.test.ts index ce2165b3..b48375a2 100644 --- a/tests/widgets/profileEditor/profileEditorWidget.test.ts +++ b/tests/widgets/profileEditor/profileEditorWidget.test.ts @@ -30,7 +30,19 @@ const defaultConfig: Config = { mfaSmsEnabled: false, mfaEmailEnabled: false, rbaEnabled: false, - consentsVersions: {}, + consentsVersions: { + 'optinTesting': { + key: 'optinTesting', + consentType: 'opt-in', + status: 'active', + versions: [{ + versionId: 1, + title: 'Opt-in Testing v1', + language: 'fr', + description: 'This is just a test' + }] + } + }, passwordPolicy: { minLength: 8, minStrength: 2, @@ -116,9 +128,21 @@ describe('DOM testing', () => { test('default', async () => { const user = userEvent.setup() - const profile = { + // @ts-expect-error partial Profile + const profile: Profile = { givenName: 'John', familyName: 'Do', + consents: { + optinTesting: { + granted: false, + consentType: 'opt-in', + consentVersion: { + versionId: 1, + language: 'fr' + }, + date: '2021-01-01T00:00:00.000Z', + } + } } getUser.mockResolvedValue(profile as Profile) @@ -128,14 +152,15 @@ describe('DOM testing', () => { await generateComponent({ fields: [ 'given_name', - 'family_name' + 'family_name', + 'optinTesting' ] }) expect(getUser).toBeCalledWith( expect.objectContaining({ accessToken: 'azerty', - fields: 'givenName,familyName', + fields: 'givenName,familyName,consents.optinTesting', }) ) @@ -152,6 +177,10 @@ describe('DOM testing', () => { await userEvent.clear(familyNameInput) await userEvent.type(familyNameInput, 'reachfive') + // const consentCheckbox = screen.getByTestId('consents.optinTesting.1') + const consentCheckbox = screen.getByLabelText(defaultConfig.consentsVersions['optinTesting'].versions[0].title) + await user.click(consentCheckbox) + const submitBtn = screen.getByRole('button', { name: 'save'}) expect(submitBtn).toBeInTheDocument() @@ -163,6 +192,16 @@ describe('DOM testing', () => { data: { givenName: 'alice', familyName: 'reachfive', + consents: { + optin_testing: { // consent key should be snakecase + granted: true, + consentType: 'opt-in', + consentVersion: { + versionId: 1, + language: 'fr' + } + } + } } }) )