Refactor kcContext, avoid having mocks in the dist https://github.com/keycloakify/keycloakify/discussions/299#discussioncomment-9616747
This commit is contained in:
@ -3,9 +3,12 @@ import Fallback from "keycloakify/login/Fallback";
|
||||
export default Fallback;
|
||||
|
||||
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
|
||||
export { getKcContext } from "keycloakify/login/kcContext/getKcContext";
|
||||
export { createGetKcContext } from "keycloakify/login/kcContext/createGetKcContext";
|
||||
export type { LoginThemePageId as PageId } from "keycloakify/bin/shared/constants";
|
||||
export { createUseI18n } from "keycloakify/login/i18n/i18n";
|
||||
export type {
|
||||
ExtendKcContext,
|
||||
Attribute,
|
||||
PasswordPolicies
|
||||
} from "keycloakify/login/kcContext";
|
||||
export { createGetKcContextMock } from "keycloakify/login/kcContext";
|
||||
|
||||
export type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
|
@ -3,14 +3,28 @@ import type {
|
||||
LoginThemePageId,
|
||||
nameOfTheLocalizationRealmOverridesUserProfileProperty
|
||||
} from "keycloakify/bin/shared/constants";
|
||||
import type { ExtractAfterStartingWith } from "keycloakify/tools/ExtractAfterStartingWith";
|
||||
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { MessageKey } from "../i18n/i18n";
|
||||
|
||||
type ExtractAfterStartingWith<
|
||||
Prefix extends string,
|
||||
StrEnum
|
||||
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||
export type ExtendKcContext<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<string, Record<string, unknown>>
|
||||
> = ValueOf<{
|
||||
[PageId in keyof KcContextExtraPropertiesPerPage | KcContext["pageId"]]: Extract<
|
||||
KcContext,
|
||||
{ pageId: PageId }
|
||||
> extends never
|
||||
? KcContext.Common &
|
||||
KcContextExtraProperties & {
|
||||
pageId: PageId;
|
||||
} & KcContextExtraPropertiesPerPage[PageId]
|
||||
: Extract<KcContext, { pageId: PageId }> &
|
||||
KcContextExtraProperties &
|
||||
KcContextExtraPropertiesPerPage[PageId];
|
||||
}>;
|
||||
|
||||
/** Take theses type definition with a grain of salt.
|
||||
* Some values might be undefined on some pages.
|
||||
@ -138,12 +152,12 @@ export declare namespace KcContext {
|
||||
|
||||
getFirstError: (...fieldNames: string[]) => string;
|
||||
};
|
||||
properties: Record<string, string | undefined>;
|
||||
authenticationSession?: {
|
||||
authSessionId: string;
|
||||
tabId: string;
|
||||
ssoLoginInOtherTabsUrl: string;
|
||||
};
|
||||
properties: {};
|
||||
__localizationRealmOverridesUserProfile?: Record<string, string>;
|
||||
};
|
||||
|
||||
@ -585,7 +599,7 @@ export declare namespace KcContext {
|
||||
}
|
||||
|
||||
export type UserProfile = {
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
html5DataAnnotations?: Record<string, string>;
|
||||
};
|
||||
|
||||
@ -683,31 +697,31 @@ export type Attribute = {
|
||||
| "photo";
|
||||
};
|
||||
|
||||
export type Validators = Partial<{
|
||||
length: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
integer: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
email: Validators.DoIgnoreEmpty;
|
||||
pattern: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
||||
options: Validators.Options;
|
||||
multivalued: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
export type Validators = {
|
||||
length?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
integer?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
email?: Validators.DoIgnoreEmpty;
|
||||
pattern?: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
||||
options?: Validators.Options;
|
||||
multivalued?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
// NOTE: Following are the validators for which we don't implement client side validation yet
|
||||
// or for which the validation can't be performed on the client side.
|
||||
/*
|
||||
double: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
"up-immutable-attribute": {};
|
||||
"up-attribute-required-by-metadata-value": {};
|
||||
"up-username-has-value": {};
|
||||
"up-duplicate-username": {};
|
||||
"up-username-mutation": {};
|
||||
"up-email-exists-as-username": {};
|
||||
"up-blank-attribute-value": Validators.ErrorMessage & { "fail-on-null": boolean; };
|
||||
"up-duplicate-email": {};
|
||||
"local-date": Validators.DoIgnoreEmpty;
|
||||
"person-name-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
uri: Validators.DoIgnoreEmpty;
|
||||
"username-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
double?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||
"up-immutable-attribute"?: {};
|
||||
"up-attribute-required-by-metadata-value"?: {};
|
||||
"up-username-has-value"?: {};
|
||||
"up-duplicate-username"?: {};
|
||||
"up-username-mutation"?: {};
|
||||
"up-email-exists-as-username"?: {};
|
||||
"up-blank-attribute-value"?: Validators.ErrorMessage & { "fail-on-null": boolean; };
|
||||
"up-duplicate-email"?: {};
|
||||
"local-date"?: Validators.DoIgnoreEmpty;
|
||||
"person-name-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
uri?: Validators.DoIgnoreEmpty;
|
||||
"username-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||
*/
|
||||
}>;
|
||||
};
|
||||
|
||||
export declare namespace Validators {
|
||||
export type DoIgnoreEmpty = {
|
||||
|
@ -1,199 +0,0 @@
|
||||
import type { KcContext, Attribute } from "./KcContext";
|
||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||
import { id } from "tsafe/id";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||
import { symToStr } from "tsafe/symToStr";
|
||||
|
||||
export function createGetKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
mockProperties?: Record<string, string>;
|
||||
}) {
|
||||
const { mockData, mockProperties } = params ?? {};
|
||||
|
||||
function getKcContext<
|
||||
PageId extends
|
||||
| ExtendKcContext<KcContextExtension>["pageId"]
|
||||
| undefined = undefined
|
||||
>(params?: {
|
||||
mockPageId?: PageId;
|
||||
storyPartialKcContext?: DeepPartial<
|
||||
Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>
|
||||
>;
|
||||
}): {
|
||||
kcContext: PageId extends undefined
|
||||
? ExtendKcContext<KcContextExtension> | undefined
|
||||
: Extract<ExtendKcContext<KcContextExtension>, { pageId: PageId }>;
|
||||
} {
|
||||
const { mockPageId, storyPartialKcContext } = params ?? {};
|
||||
|
||||
const realKcContext = getKcContextFromWindow<KcContextExtension>();
|
||||
|
||||
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||
//TODO maybe trow if no mock fo custom page
|
||||
|
||||
warn_that_mock_is_enabled: {
|
||||
if (isStorybook) {
|
||||
break warn_that_mock_is_enabled;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`%cKeycloakify: ${symToStr({
|
||||
mockPageId
|
||||
})} set to ${mockPageId}.`,
|
||||
"background: red; color: yellow; font-size: medium"
|
||||
);
|
||||
}
|
||||
|
||||
const kcContextDefaultMock = kcContextMocks.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
const partialKcContextCustomMock = (() => {
|
||||
const out: DeepPartial<ExtendKcContext<KcContextExtension>> = {};
|
||||
|
||||
const mockDataPick = mockData?.find(
|
||||
({ pageId }) => pageId === mockPageId
|
||||
);
|
||||
|
||||
if (mockDataPick !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: mockDataPick
|
||||
});
|
||||
}
|
||||
|
||||
if (storyPartialKcContext !== undefined) {
|
||||
deepAssign({
|
||||
target: out,
|
||||
source: storyPartialKcContext
|
||||
});
|
||||
}
|
||||
|
||||
return Object.keys(out).length === 0 ? undefined : out;
|
||||
})();
|
||||
|
||||
if (
|
||||
kcContextDefaultMock === undefined &&
|
||||
partialKcContextCustomMock === undefined
|
||||
) {
|
||||
console.warn(
|
||||
[
|
||||
`WARNING: You declared the non build in page ${mockPageId} but you didn't `,
|
||||
`provide mock data needed to debug the page outside of Keycloak as you are trying to do now.`,
|
||||
`Please check the documentation of the getKcContext function`
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
const kcContext: any = {};
|
||||
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source:
|
||||
kcContextDefaultMock !== undefined
|
||||
? kcContextDefaultMock
|
||||
: { pageId: mockPageId, ...kcContextCommonMock }
|
||||
});
|
||||
|
||||
if (partialKcContextCustomMock !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext,
|
||||
source: partialKcContextCustomMock
|
||||
});
|
||||
|
||||
if ("profile" in partialKcContextCustomMock) {
|
||||
assert(
|
||||
kcContextDefaultMock !== undefined &&
|
||||
"profile" in kcContextDefaultMock
|
||||
);
|
||||
|
||||
const { attributes } = kcContextDefaultMock.profile;
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes = [];
|
||||
|
||||
const partialAttributes = [
|
||||
...((
|
||||
partialKcContextCustomMock as DeepPartial<KcContext.Register>
|
||||
).profile?.attributes ?? [])
|
||||
].filter(exclude(undefined));
|
||||
|
||||
attributes.forEach(attribute => {
|
||||
const partialAttribute = partialAttributes.find(
|
||||
({ name }) => name === attribute.name
|
||||
);
|
||||
|
||||
const augmentedAttribute: Attribute = {} as any;
|
||||
|
||||
deepAssign({
|
||||
target: augmentedAttribute,
|
||||
source: attribute
|
||||
});
|
||||
|
||||
if (partialAttribute !== undefined) {
|
||||
partialAttributes.splice(
|
||||
partialAttributes.indexOf(partialAttribute),
|
||||
1
|
||||
);
|
||||
|
||||
deepAssign({
|
||||
target: augmentedAttribute,
|
||||
source: partialAttribute
|
||||
});
|
||||
}
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
augmentedAttribute
|
||||
);
|
||||
});
|
||||
|
||||
partialAttributes
|
||||
.map(partialAttribute => ({
|
||||
validators: {},
|
||||
...partialAttribute
|
||||
}))
|
||||
.forEach(partialAttribute => {
|
||||
const { name } = partialAttribute;
|
||||
|
||||
assert(
|
||||
name !== undefined,
|
||||
"If you define a mock attribute it must have at least a name"
|
||||
);
|
||||
|
||||
id<KcContext.Register>(kcContext).profile.attributes.push(
|
||||
partialAttribute as any
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (mockProperties !== undefined) {
|
||||
deepAssign({
|
||||
target: kcContext.properties,
|
||||
source: mockProperties
|
||||
});
|
||||
}
|
||||
|
||||
return { kcContext };
|
||||
}
|
||||
|
||||
if (realKcContext === undefined) {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
if (realKcContext.themeType !== "login") {
|
||||
return { kcContext: undefined as any };
|
||||
}
|
||||
|
||||
return { kcContext: realKcContext as any };
|
||||
}
|
||||
|
||||
return { getKcContext };
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||
import { createGetKcContext } from "./createGetKcContext";
|
||||
|
||||
/** NOTE: We now recommend using createGetKcContext instead of this function to make storybook integration easier
|
||||
* See: https://github.com/keycloakify/keycloakify-starter/blob/main/src/keycloak-theme/account/kcContext.ts
|
||||
*/
|
||||
export function getKcContext<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(params?: {
|
||||
mockPageId?: ExtendKcContext<KcContextExtension>["pageId"];
|
||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||
}): { kcContext: ExtendKcContext<KcContextExtension> | undefined } {
|
||||
const { mockPageId, mockData } = params ?? {};
|
||||
|
||||
const { getKcContext } = createGetKcContext<KcContextExtension>({
|
||||
mockData
|
||||
});
|
||||
|
||||
const { kcContext } = getKcContext({ mockPageId });
|
||||
|
||||
return { kcContext };
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import type { KcContext } from "./KcContext";
|
||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
|
||||
|
||||
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [
|
||||
KcContextExtension
|
||||
] extends [never]
|
||||
? KcContext
|
||||
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
||||
|
||||
export function getKcContextFromWindow<
|
||||
KcContextExtension extends { pageId: string } = never
|
||||
>(): ExtendKcContext<KcContextExtension> | undefined {
|
||||
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
|
||||
}
|
80
src/login/kcContext/getKcContextMock.ts
Normal file
80
src/login/kcContext/getKcContextMock.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
|
||||
import type { LoginThemePageId } from "keycloakify/bin/shared/constants";
|
||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
|
||||
export function createGetKcContextMock<
|
||||
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||
KcContextExtraPropertiesPerPage extends Record<
|
||||
`${string}.ftl`,
|
||||
Record<string, unknown>
|
||||
>
|
||||
>(params: {
|
||||
kcContextExtraProperties: KcContextExtraProperties;
|
||||
kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage;
|
||||
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
|
||||
overridesPerPage?: {
|
||||
[PageId in
|
||||
| LoginThemePageId
|
||||
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
|
||||
Extract<
|
||||
ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>,
|
||||
{ pageId: PageId }
|
||||
>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const {
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage,
|
||||
overrides: overrides_global,
|
||||
overridesPerPage: overridesPerPage_global
|
||||
} = params;
|
||||
|
||||
type KcContext = ExtendKcContext<
|
||||
KcContextExtraProperties,
|
||||
KcContextExtraPropertiesPerPage
|
||||
>;
|
||||
|
||||
function getKcContextMock<
|
||||
PageId extends LoginThemePageId | keyof KcContextExtraPropertiesPerPage
|
||||
>(params: {
|
||||
pageId: PageId;
|
||||
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
|
||||
}): Extract<KcContext, { pageId: PageId }> {
|
||||
const { pageId, overrides } = params;
|
||||
|
||||
const kcContextMock = structuredCloneButFunctions(
|
||||
kcContextMocks.find(kcContextMock => kcContextMock.pageId === pageId) ?? {
|
||||
...kcContextCommonMock,
|
||||
pageId
|
||||
}
|
||||
);
|
||||
|
||||
[
|
||||
kcContextExtraProperties,
|
||||
kcContextExtraPropertiesPerPage[pageId],
|
||||
overrides_global,
|
||||
overridesPerPage_global?.[pageId],
|
||||
overrides
|
||||
]
|
||||
.filter(exclude(undefined))
|
||||
.forEach(overrides =>
|
||||
deepAssign({
|
||||
target: kcContextMock,
|
||||
source: overrides
|
||||
})
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
return kcContextMock;
|
||||
}
|
||||
|
||||
return { getKcContextMock };
|
||||
}
|
@ -1 +1,7 @@
|
||||
export type { KcContext } from "./KcContext";
|
||||
export type {
|
||||
ExtendKcContext,
|
||||
KcContext,
|
||||
Attribute,
|
||||
PasswordPolicies
|
||||
} from "./KcContext";
|
||||
export { createGetKcContextMock } from "./getKcContextMock";
|
||||
|
@ -9,71 +9,73 @@ import { id } from "tsafe/id";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||
|
||||
const attributes: Attribute[] = [
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
"ignore.empty.value": true,
|
||||
min: "3",
|
||||
max: "255"
|
||||
}
|
||||
},
|
||||
displayName: "${username}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "username",
|
||||
readOnly: false,
|
||||
name: "username",
|
||||
value: "xxxx"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
const attributesByName = Object.fromEntries(
|
||||
id<Attribute[]>([
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
"ignore.empty.value": true,
|
||||
min: "3",
|
||||
max: "255"
|
||||
}
|
||||
},
|
||||
email: {
|
||||
"ignore.empty.value": true
|
||||
displayName: "${username}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "username",
|
||||
readOnly: false,
|
||||
name: "username",
|
||||
value: "xxxx"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
email: {
|
||||
"ignore.empty.value": true
|
||||
},
|
||||
pattern: {
|
||||
"ignore.empty.value": true,
|
||||
pattern: "gmail\\.com$"
|
||||
}
|
||||
},
|
||||
pattern: {
|
||||
"ignore.empty.value": true,
|
||||
pattern: "gmail\\.com$"
|
||||
}
|
||||
displayName: "${email}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "email",
|
||||
readOnly: false,
|
||||
name: "email"
|
||||
},
|
||||
displayName: "${email}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
autocomplete: "email",
|
||||
readOnly: false,
|
||||
name: "email"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${firstName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "firstName"
|
||||
},
|
||||
displayName: "${firstName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "firstName"
|
||||
},
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${lastName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "lastName"
|
||||
}
|
||||
];
|
||||
{
|
||||
validators: {
|
||||
length: {
|
||||
max: "255",
|
||||
"ignore.empty.value": true
|
||||
}
|
||||
},
|
||||
displayName: "${lastName}",
|
||||
annotations: {},
|
||||
required: true,
|
||||
readOnly: false,
|
||||
name: "lastName"
|
||||
}
|
||||
]).map(attribute => [attribute.name, attribute])
|
||||
);
|
||||
|
||||
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
|
||||
|
||||
@ -265,7 +267,7 @@ export const kcContextMocks = [
|
||||
recaptchaRequired: false,
|
||||
pageId: "register.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
},
|
||||
scripts: [
|
||||
//"https://www.google.com/recaptcha/api.js"
|
||||
@ -416,7 +418,7 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
pageId: "login-update-profile.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.LoginIdpLinkConfirm>({
|
||||
@ -472,14 +474,16 @@ export const kcContextMocks = [
|
||||
...kcContextCommonMock,
|
||||
pageId: "idp-review-user-profile.ftl",
|
||||
profile: {
|
||||
attributes
|
||||
attributesByName
|
||||
}
|
||||
}),
|
||||
id<KcContext.UpdateEmail>({
|
||||
...kcContextCommonMock,
|
||||
pageId: "update-email.ftl",
|
||||
profile: {
|
||||
attributes: attributes.filter(attribute => attribute.name === "email")
|
||||
attributesByName: {
|
||||
email: attributesByName["email"]
|
||||
}
|
||||
}
|
||||
}),
|
||||
id<KcContext.SelectAuthenticator>({
|
||||
|
@ -9,7 +9,7 @@ import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/Kc
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import { formatNumber } from "keycloakify/tools/formatNumber";
|
||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||
import { structuredCloneButFunctions } from "tools/structuredCloneButFunctions";
|
||||
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export type FormFieldError = {
|
||||
@ -68,7 +68,7 @@ export type FormAction =
|
||||
export type KcContextLike = {
|
||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||
profile: {
|
||||
attributes: Attribute[];
|
||||
attributesByName: Record<string, Attribute>;
|
||||
html5DataAnnotations?: Record<string, string>;
|
||||
};
|
||||
passwordRequired?: boolean;
|
||||
@ -137,7 +137,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
|
||||
const attributes = (() => {
|
||||
retrocompat_patch: {
|
||||
if ("profile" in kcContext && "attributes" in kcContext.profile && kcContext.profile.attributes.length !== 0) {
|
||||
if ("profile" in kcContext && "attributes" in kcContext.profile && Object.keys(kcContext.profile.attributesByName).length !== 0) {
|
||||
break retrocompat_patch;
|
||||
}
|
||||
|
||||
@ -217,7 +217,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
||||
assert(false, "Unable to mock user profile from the current kcContext");
|
||||
}
|
||||
|
||||
return kcContext.profile.attributes.map(attribute_pre_group_patch => {
|
||||
return Object.values(kcContext.profile.attributesByName).map(attribute_pre_group_patch => {
|
||||
if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
|
||||
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
||||
attribute_pre_group_patch as Attribute & {
|
||||
|
Reference in New Issue
Block a user