Refactor kcContext, avoid having mocks in the dist https://github.com/keycloakify/keycloakify/discussions/299#discussioncomment-9616747
This commit is contained in:
parent
37a060c4db
commit
c1a63edd71
@ -5,7 +5,7 @@ import {
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an equivalent of process.env.PUBLIC_URL thay you can use in Webpack projects.
|
* This is an equivalent of process.env.PUBLIC_URL that you can use in Webpack projects.
|
||||||
* This works both in your main app and in your Keycloak theme.
|
* This works both in your main app and in your Keycloak theme.
|
||||||
*/
|
*/
|
||||||
export const PUBLIC_URL = (() => {
|
export const PUBLIC_URL = (() => {
|
||||||
|
@ -2,9 +2,9 @@ import Fallback from "keycloakify/account/Fallback";
|
|||||||
|
|
||||||
export default Fallback;
|
export default Fallback;
|
||||||
|
|
||||||
export { getKcContext } from "keycloakify/account/kcContext/getKcContext";
|
|
||||||
export { createGetKcContext } from "keycloakify/account/kcContext/createGetKcContext";
|
|
||||||
export type { AccountThemePageId as PageId } from "keycloakify/bin/shared/constants";
|
export type { AccountThemePageId as PageId } from "keycloakify/bin/shared/constants";
|
||||||
export { createUseI18n } from "keycloakify/account/i18n/i18n";
|
export { createUseI18n } from "keycloakify/account/i18n/i18n";
|
||||||
|
export type { ExtendKcContext } from "keycloakify/account/kcContext";
|
||||||
|
export { createGetKcContextMock } from "keycloakify/account/kcContext";
|
||||||
|
|
||||||
export type { PageProps } from "keycloakify/account/pages/PageProps";
|
export type { PageProps } from "keycloakify/account/pages/PageProps";
|
||||||
|
@ -1,6 +1,24 @@
|
|||||||
|
import type { ThemeType, AccountThemePageId } from "keycloakify/bin/shared/constants";
|
||||||
|
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import type { ThemeType, AccountThemePageId } from "keycloakify/bin/shared/constants";
|
|
||||||
|
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];
|
||||||
|
}>;
|
||||||
|
|
||||||
export type KcContext =
|
export type KcContext =
|
||||||
| KcContext.Password
|
| KcContext.Password
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
|
||||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
|
||||||
import { isStorybook } from "keycloakify/lib/isStorybook";
|
|
||||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
|
||||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
|
||||||
import { symToStr } from "tsafe/symToStr";
|
|
||||||
import {
|
|
||||||
kcContextMocks,
|
|
||||||
kcContextCommonMock
|
|
||||||
} from "keycloakify/account/kcContext/kcContextMocks";
|
|
||||||
|
|
||||||
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_enbaled: {
|
|
||||||
if (isStorybook) {
|
|
||||||
break warn_that_mock_is_enbaled;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (mockProperties !== undefined) {
|
|
||||||
deepAssign({
|
|
||||||
target: kcContext.properties,
|
|
||||||
source: mockProperties
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { kcContext };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realKcContext === undefined) {
|
|
||||||
return { kcContext: undefined as any };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realKcContext.themeType !== "account") {
|
|
||||||
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({
|
|
||||||
mockData
|
|
||||||
});
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({ mockPageId });
|
|
||||||
|
|
||||||
return { kcContext };
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
|
||||||
import { nameOfTheGlobal } from "keycloakify/bin/shared/constants";
|
|
||||||
import type { KcContext } from "./KcContext";
|
|
||||||
|
|
||||||
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/account/kcContext/getKcContextMock.ts
Normal file
80
src/account/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,2 @@
|
|||||||
export type { KcContext } from "./KcContext";
|
export type { ExtendKcContext, KcContext } from "./KcContext";
|
||||||
|
export { createGetKcContextMock } from "./getKcContextMock";
|
||||||
|
@ -288,13 +288,11 @@ function decodeHtmlEntities(htmlStr){
|
|||||||
are_same_path(path, [])
|
are_same_path(path, [])
|
||||||
) || (
|
) || (
|
||||||
<#-- attributesByName adds a lot of noise to the output and is not needed -->
|
<#-- attributesByName adds a lot of noise to the output and is not needed -->
|
||||||
key == "attributesByName" &&
|
|
||||||
(
|
|
||||||
are_same_path(path, ["profile"]) ||
|
|
||||||
are_same_path(path, ["register"])
|
|
||||||
)
|
|
||||||
) || (
|
|
||||||
key == "attributes" &&
|
key == "attributes" &&
|
||||||
|
are_same_path(path, ["profile"])
|
||||||
|
) || (
|
||||||
|
<#-- We already have the attributes in profile speedup the rendering by filtering it out from the register object -->
|
||||||
|
(key == "attributes" || key == "attributesByName") &&
|
||||||
are_same_path(path, ["register"])
|
are_same_path(path, ["register"])
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
|
@ -3,9 +3,12 @@ import Fallback from "keycloakify/login/Fallback";
|
|||||||
export default Fallback;
|
export default Fallback;
|
||||||
|
|
||||||
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
|
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 { 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";
|
export type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
|
@ -3,14 +3,28 @@ import type {
|
|||||||
LoginThemePageId,
|
LoginThemePageId,
|
||||||
nameOfTheLocalizationRealmOverridesUserProfileProperty
|
nameOfTheLocalizationRealmOverridesUserProfileProperty
|
||||||
} from "keycloakify/bin/shared/constants";
|
} 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 { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import type { MessageKey } from "../i18n/i18n";
|
import type { MessageKey } from "../i18n/i18n";
|
||||||
|
|
||||||
type ExtractAfterStartingWith<
|
export type ExtendKcContext<
|
||||||
Prefix extends string,
|
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
|
||||||
StrEnum
|
KcContextExtraPropertiesPerPage extends Record<string, Record<string, unknown>>
|
||||||
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
> = 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.
|
/** Take theses type definition with a grain of salt.
|
||||||
* Some values might be undefined on some pages.
|
* Some values might be undefined on some pages.
|
||||||
@ -138,12 +152,12 @@ export declare namespace KcContext {
|
|||||||
|
|
||||||
getFirstError: (...fieldNames: string[]) => string;
|
getFirstError: (...fieldNames: string[]) => string;
|
||||||
};
|
};
|
||||||
properties: Record<string, string | undefined>;
|
|
||||||
authenticationSession?: {
|
authenticationSession?: {
|
||||||
authSessionId: string;
|
authSessionId: string;
|
||||||
tabId: string;
|
tabId: string;
|
||||||
ssoLoginInOtherTabsUrl: string;
|
ssoLoginInOtherTabsUrl: string;
|
||||||
};
|
};
|
||||||
|
properties: {};
|
||||||
__localizationRealmOverridesUserProfile?: Record<string, string>;
|
__localizationRealmOverridesUserProfile?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -585,7 +599,7 @@ export declare namespace KcContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UserProfile = {
|
export type UserProfile = {
|
||||||
attributes: Attribute[];
|
attributesByName: Record<string, Attribute>;
|
||||||
html5DataAnnotations?: Record<string, string>;
|
html5DataAnnotations?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -683,31 +697,31 @@ export type Attribute = {
|
|||||||
| "photo";
|
| "photo";
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Validators = Partial<{
|
export type Validators = {
|
||||||
length: Validators.DoIgnoreEmpty & Validators.Range;
|
length?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||||
integer: Validators.DoIgnoreEmpty & Validators.Range;
|
integer?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||||
email: Validators.DoIgnoreEmpty;
|
email?: Validators.DoIgnoreEmpty;
|
||||||
pattern: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
pattern?: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
|
||||||
options: Validators.Options;
|
options?: Validators.Options;
|
||||||
multivalued: Validators.DoIgnoreEmpty & Validators.Range;
|
multivalued?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||||
// NOTE: Following are the validators for which we don't implement client side validation yet
|
// 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.
|
// or for which the validation can't be performed on the client side.
|
||||||
/*
|
/*
|
||||||
double: Validators.DoIgnoreEmpty & Validators.Range;
|
double?: Validators.DoIgnoreEmpty & Validators.Range;
|
||||||
"up-immutable-attribute": {};
|
"up-immutable-attribute"?: {};
|
||||||
"up-attribute-required-by-metadata-value": {};
|
"up-attribute-required-by-metadata-value"?: {};
|
||||||
"up-username-has-value": {};
|
"up-username-has-value"?: {};
|
||||||
"up-duplicate-username": {};
|
"up-duplicate-username"?: {};
|
||||||
"up-username-mutation": {};
|
"up-username-mutation"?: {};
|
||||||
"up-email-exists-as-username": {};
|
"up-email-exists-as-username"?: {};
|
||||||
"up-blank-attribute-value": Validators.ErrorMessage & { "fail-on-null": boolean; };
|
"up-blank-attribute-value"?: Validators.ErrorMessage & { "fail-on-null": boolean; };
|
||||||
"up-duplicate-email": {};
|
"up-duplicate-email"?: {};
|
||||||
"local-date": Validators.DoIgnoreEmpty;
|
"local-date"?: Validators.DoIgnoreEmpty;
|
||||||
"person-name-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
"person-name-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||||
uri: Validators.DoIgnoreEmpty;
|
uri?: Validators.DoIgnoreEmpty;
|
||||||
"username-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
"username-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
|
||||||
*/
|
*/
|
||||||
}>;
|
};
|
||||||
|
|
||||||
export declare namespace Validators {
|
export declare namespace Validators {
|
||||||
export type DoIgnoreEmpty = {
|
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,7 +9,8 @@ import { id } from "tsafe/id";
|
|||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||||
|
|
||||||
const attributes: Attribute[] = [
|
const attributesByName = Object.fromEntries(
|
||||||
|
id<Attribute[]>([
|
||||||
{
|
{
|
||||||
validators: {
|
validators: {
|
||||||
length: {
|
length: {
|
||||||
@ -73,7 +74,8 @@ const attributes: Attribute[] = [
|
|||||||
readOnly: false,
|
readOnly: false,
|
||||||
name: "lastName"
|
name: "lastName"
|
||||||
}
|
}
|
||||||
];
|
]).map(attribute => [attribute.name, attribute])
|
||||||
|
);
|
||||||
|
|
||||||
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
|
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
|
||||||
|
|
||||||
@ -265,7 +267,7 @@ export const kcContextMocks = [
|
|||||||
recaptchaRequired: false,
|
recaptchaRequired: false,
|
||||||
pageId: "register.ftl",
|
pageId: "register.ftl",
|
||||||
profile: {
|
profile: {
|
||||||
attributes
|
attributesByName
|
||||||
},
|
},
|
||||||
scripts: [
|
scripts: [
|
||||||
//"https://www.google.com/recaptcha/api.js"
|
//"https://www.google.com/recaptcha/api.js"
|
||||||
@ -416,7 +418,7 @@ export const kcContextMocks = [
|
|||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
pageId: "login-update-profile.ftl",
|
pageId: "login-update-profile.ftl",
|
||||||
profile: {
|
profile: {
|
||||||
attributes
|
attributesByName
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
id<KcContext.LoginIdpLinkConfirm>({
|
id<KcContext.LoginIdpLinkConfirm>({
|
||||||
@ -472,14 +474,16 @@ export const kcContextMocks = [
|
|||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
pageId: "idp-review-user-profile.ftl",
|
pageId: "idp-review-user-profile.ftl",
|
||||||
profile: {
|
profile: {
|
||||||
attributes
|
attributesByName
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
id<KcContext.UpdateEmail>({
|
id<KcContext.UpdateEmail>({
|
||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
pageId: "update-email.ftl",
|
pageId: "update-email.ftl",
|
||||||
profile: {
|
profile: {
|
||||||
attributes: attributes.filter(attribute => attribute.name === "email")
|
attributesByName: {
|
||||||
|
email: attributesByName["email"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
id<KcContext.SelectAuthenticator>({
|
id<KcContext.SelectAuthenticator>({
|
||||||
|
@ -9,7 +9,7 @@ import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/Kc
|
|||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import { formatNumber } from "keycloakify/tools/formatNumber";
|
import { formatNumber } from "keycloakify/tools/formatNumber";
|
||||||
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
import { createUseInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
||||||
import { structuredCloneButFunctions } from "tools/structuredCloneButFunctions";
|
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
|
||||||
export type FormFieldError = {
|
export type FormFieldError = {
|
||||||
@ -68,7 +68,7 @@ export type FormAction =
|
|||||||
export type KcContextLike = {
|
export type KcContextLike = {
|
||||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||||
profile: {
|
profile: {
|
||||||
attributes: Attribute[];
|
attributesByName: Record<string, Attribute>;
|
||||||
html5DataAnnotations?: Record<string, string>;
|
html5DataAnnotations?: Record<string, string>;
|
||||||
};
|
};
|
||||||
passwordRequired?: boolean;
|
passwordRequired?: boolean;
|
||||||
@ -137,7 +137,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
|
|
||||||
const attributes = (() => {
|
const attributes = (() => {
|
||||||
retrocompat_patch: {
|
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;
|
break retrocompat_patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
assert(false, "Unable to mock user profile from the current kcContext");
|
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 !== "") {
|
if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
|
||||||
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
||||||
attribute_pre_group_patch as Attribute & {
|
attribute_pre_group_patch as Attribute & {
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
export type AndByDiscriminatingKey<
|
|
||||||
DiscriminatingKey extends string,
|
|
||||||
U1 extends Record<DiscriminatingKey, string>,
|
|
||||||
U2 extends Record<DiscriminatingKey, string>
|
|
||||||
> = AndByDiscriminatingKey.Tf1<DiscriminatingKey, U1, U1, U2>;
|
|
||||||
|
|
||||||
export declare namespace AndByDiscriminatingKey {
|
|
||||||
export type Tf1<
|
|
||||||
DiscriminatingKey extends string,
|
|
||||||
U1,
|
|
||||||
U1Again extends Record<DiscriminatingKey, string>,
|
|
||||||
U2 extends Record<DiscriminatingKey, string>
|
|
||||||
> =
|
|
||||||
U1 extends Pick<U2, DiscriminatingKey>
|
|
||||||
? Tf2<DiscriminatingKey, U1, U2, U1Again>
|
|
||||||
: U1Again[DiscriminatingKey] & U2[DiscriminatingKey] extends never
|
|
||||||
? U1 | U2
|
|
||||||
: U1;
|
|
||||||
|
|
||||||
export type Tf2<
|
|
||||||
DiscriminatingKey extends string,
|
|
||||||
SingletonU1 extends Record<DiscriminatingKey, string>,
|
|
||||||
U2,
|
|
||||||
U1 extends Record<DiscriminatingKey, string>
|
|
||||||
> =
|
|
||||||
U2 extends Pick<SingletonU1, DiscriminatingKey>
|
|
||||||
? U2 & SingletonU1
|
|
||||||
: U2 extends Pick<U1, DiscriminatingKey>
|
|
||||||
? never
|
|
||||||
: U2;
|
|
||||||
}
|
|
4
src/tools/ExtractAfterStartingWith.ts
Normal file
4
src/tools/ExtractAfterStartingWith.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type ExtractAfterStartingWith<
|
||||||
|
Prefix extends string,
|
||||||
|
StrEnum
|
||||||
|
> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
2
src/tools/ValueOf.ts
Normal file
2
src/tools/ValueOf.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/** Pendant of `keyof T` */
|
||||||
|
export type ValueOf<T> = T[keyof T];
|
@ -2,44 +2,60 @@ import { assert } from "tsafe/assert";
|
|||||||
import { is } from "tsafe/is";
|
import { is } from "tsafe/is";
|
||||||
import { structuredCloneButFunctions } from "./structuredCloneButFunctions";
|
import { structuredCloneButFunctions } from "./structuredCloneButFunctions";
|
||||||
|
|
||||||
//Warning: Be mindful that because of array this is not idempotent.
|
/** NOTE: Array a copied over, not merged. */
|
||||||
export function deepAssign(params: {
|
export function deepAssign(params: {
|
||||||
target: Record<string, unknown>;
|
target: Record<string, unknown>;
|
||||||
source: Record<string, unknown>;
|
source: Record<string, unknown>;
|
||||||
}) {
|
}): void {
|
||||||
const { target } = params;
|
const { target, source } = params;
|
||||||
|
|
||||||
const source = structuredCloneButFunctions(params.source);
|
|
||||||
|
|
||||||
Object.keys(source).forEach(key => {
|
Object.keys(source).forEach(key => {
|
||||||
var dereferencedSource = source[key];
|
var dereferencedSource = source[key];
|
||||||
|
|
||||||
|
if (dereferencedSource === undefined) {
|
||||||
|
delete target[key];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dereferencedSource instanceof Date) {
|
||||||
|
assign({
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
value: new Date(dereferencedSource.getTime())
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dereferencedSource instanceof Array) {
|
||||||
|
assign({
|
||||||
|
target,
|
||||||
|
key,
|
||||||
|
value: structuredCloneButFunctions(dereferencedSource)
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
target[key] === undefined ||
|
|
||||||
dereferencedSource instanceof Function ||
|
dereferencedSource instanceof Function ||
|
||||||
!(dereferencedSource instanceof Object)
|
!(dereferencedSource instanceof Object)
|
||||||
) {
|
) {
|
||||||
Object.defineProperty(target, key, {
|
assign({
|
||||||
enumerable: true,
|
target,
|
||||||
writable: true,
|
key,
|
||||||
configurable: true,
|
|
||||||
value: dereferencedSource
|
value: dereferencedSource
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dereferencedTarget = target[key];
|
if (!(target[key] instanceof Object)) {
|
||||||
|
target[key] = {};
|
||||||
if (dereferencedSource instanceof Array) {
|
|
||||||
assert(is<unknown[]>(dereferencedTarget));
|
|
||||||
assert(is<unknown[]>(dereferencedSource));
|
|
||||||
|
|
||||||
dereferencedSource.forEach(entry => dereferencedTarget.push(entry));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dereferencedTarget = target[key];
|
||||||
|
|
||||||
assert(is<Record<string, unknown>>(dereferencedTarget));
|
assert(is<Record<string, unknown>>(dereferencedTarget));
|
||||||
assert(is<Record<string, unknown>>(dereferencedSource));
|
assert(is<Record<string, unknown>>(dereferencedSource));
|
||||||
|
|
||||||
@ -49,3 +65,18 @@ export function deepAssign(params: {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assign(params: {
|
||||||
|
target: Record<string, unknown>;
|
||||||
|
key: string;
|
||||||
|
value: unknown;
|
||||||
|
}): void {
|
||||||
|
const { target, key, value } = params;
|
||||||
|
|
||||||
|
Object.defineProperty(target, key, {
|
||||||
|
enumerable: true,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { getKcContext, type KcContext } from "./kcContext";
|
import type { KcContext } from "./kcContext";
|
||||||
|
import { getKcContextMock } from "./kcContextMock";
|
||||||
import KcApp from "./KcApp";
|
import KcApp from "./KcApp";
|
||||||
import type { DeepPartial } from "../../dist/tools/DeepPartial";
|
import type { DeepPartial } from "../../dist/tools/DeepPartial";
|
||||||
|
|
||||||
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
|
export function createPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
|
||||||
const { pageId } = params;
|
const { pageId } = params;
|
||||||
|
|
||||||
function PageStory(params: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
function PageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
|
||||||
const { kcContext } = getKcContext({
|
const { kcContext: overrides } = props;
|
||||||
mockPageId: pageId,
|
|
||||||
storyPartialKcContext: params.kcContext
|
const kcContextMock = getKcContextMock({
|
||||||
|
pageId,
|
||||||
|
overrides
|
||||||
});
|
});
|
||||||
|
|
||||||
return <KcApp kcContext={kcContext} />;
|
return <KcApp kcContext={kcContextMock} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { PageStory };
|
return { PageStory };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parameters = {
|
||||||
|
viewMode: "story",
|
||||||
|
previewTabs: {
|
||||||
|
"storybook/docs/panel": {
|
||||||
|
hidden: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { createGetKcContext } from "../../dist/account";
|
import type { ExtendKcContext } from "../../dist/account";
|
||||||
|
|
||||||
export const { getKcContext } = createGetKcContext();
|
export type KcContextExtraProperties = {};
|
||||||
|
|
||||||
const { kcContext } = getKcContext();
|
export type KcContextExtraPropertiesPerPage = {};
|
||||||
|
|
||||||
export type KcContext = NonNullable<typeof kcContext>;
|
export type KcContext = ExtendKcContext<
|
||||||
|
KcContextExtraProperties,
|
||||||
|
KcContextExtraPropertiesPerPage
|
||||||
|
>;
|
||||||
|
13
stories/account/kcContextMock.ts
Normal file
13
stories/account/kcContextMock.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { createGetKcContextMock } from "../../dist/account";
|
||||||
|
import type {
|
||||||
|
KcContextExtraProperties,
|
||||||
|
KcContextExtraPropertiesPerPage
|
||||||
|
} from "./kcContext";
|
||||||
|
|
||||||
|
const kcContextExtraProperties: KcContextExtraProperties = {};
|
||||||
|
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
|
||||||
|
|
||||||
|
export const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtraProperties,
|
||||||
|
kcContextExtraPropertiesPerPage
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
import { ExtendKcContext } from "../../dist/login";
|
import type { ExtendKcContext } from "../../dist/login";
|
||||||
|
|
||||||
export type KcContextExtraProperties = {};
|
export type KcContextExtraProperties = {};
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { createGetKcContextMock } from "../../dist/login";
|
import { createGetKcContextMock } from "../../dist/login";
|
||||||
import { KcContextExtraProperties, KcContextExtraPropertiesPerPage } from "./kcContext";
|
import type {
|
||||||
|
KcContextExtraProperties,
|
||||||
|
KcContextExtraPropertiesPerPage
|
||||||
|
} from "./kcContext";
|
||||||
|
|
||||||
const kcContextExtraProperties: KcContextExtraProperties = {};
|
const kcContextExtraProperties: KcContextExtraProperties = {};
|
||||||
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
|
const kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage = {};
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
||||||
import { same } from "evt/tools/inDepth/same";
|
import { same } from "evt/tools/inDepth/same";
|
||||||
import { expect, it, describe } from "vitest";
|
import { expect, it, describe } from "vitest";
|
||||||
import { isSameCode } from "../tools/isSameCode";
|
|
||||||
import {
|
import {
|
||||||
basenameOfTheKeycloakifyResourcesDir,
|
basenameOfTheKeycloakifyResourcesDir,
|
||||||
nameOfTheGlobal
|
nameOfTheGlobal
|
||||||
@ -664,3 +663,10 @@ describe("inline css replacer", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function isSameCode(code1: string, code2: string): boolean {
|
||||||
|
const removeSpacesAndNewLines = (code: string) =>
|
||||||
|
code.replace(/\s/g, "").replace(/\n/g, "");
|
||||||
|
|
||||||
|
return removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2);
|
||||||
|
}
|
||||||
|
@ -1,303 +0,0 @@
|
|||||||
import { createGetKcContext } from "keycloakify/login/kcContext/createGetKcContext";
|
|
||||||
import type { ExtendKcContext } from "keycloakify/login/kcContext/getKcContextFromWindow";
|
|
||||||
import type { KcContext } from "keycloakify/login/kcContext";
|
|
||||||
import { same } from "evt/tools/inDepth";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import type { Equals } from "tsafe";
|
|
||||||
import {
|
|
||||||
kcContextMocks,
|
|
||||||
kcContextCommonMock
|
|
||||||
} from "keycloakify/login/kcContext/kcContextMocks";
|
|
||||||
import { deepClone } from "keycloakify/tools/deepClone";
|
|
||||||
import { expect, it, describe } from "vitest";
|
|
||||||
|
|
||||||
describe("createGetKcContext", () => {
|
|
||||||
const authorizedMailDomains = [
|
|
||||||
"example.com",
|
|
||||||
"another-example.com",
|
|
||||||
"*.yet-another-example.com",
|
|
||||||
"*.example.com",
|
|
||||||
"hello-world.com"
|
|
||||||
];
|
|
||||||
|
|
||||||
const displayName = "this is an overwritten common value";
|
|
||||||
|
|
||||||
const aNonStandardValue1 = "a non standard value 1";
|
|
||||||
const aNonStandardValue2 = "a non standard value 2";
|
|
||||||
|
|
||||||
type KcContextExtension =
|
|
||||||
| {
|
|
||||||
pageId: "register.ftl";
|
|
||||||
authorizedMailDomains: string[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "info.ftl";
|
|
||||||
aNonStandardValue1: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "my-extra-page-1.ftl";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "my-extra-page-2.ftl";
|
|
||||||
aNonStandardValue2: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getKcContextProxy = (params: {
|
|
||||||
mockPageId: ExtendKcContext<KcContextExtension>["pageId"];
|
|
||||||
}) => {
|
|
||||||
const { mockPageId } = params;
|
|
||||||
|
|
||||||
const { getKcContext } = createGetKcContext<KcContextExtension>({
|
|
||||||
mockData: [
|
|
||||||
{
|
|
||||||
pageId: "login.ftl",
|
|
||||||
realm: { displayName }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "info.ftl",
|
|
||||||
aNonStandardValue1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "register.ftl",
|
|
||||||
authorizedMailDomains
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "my-extra-page-2.ftl",
|
|
||||||
aNonStandardValue2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId
|
|
||||||
});
|
|
||||||
|
|
||||||
return { kcContext };
|
|
||||||
};
|
|
||||||
it("has proper API for login.ftl", () => {
|
|
||||||
const pageId = "login.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login>>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
//NOTE: deepClone for printIfExists or other functions...
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
mock.realm.displayName = displayName;
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a proper API for info.ftl", () => {
|
|
||||||
const pageId = "info.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Info & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
aNonStandardValue1: string;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.assign(mock, { aNonStandardValue1 });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for register.ftl", () => {
|
|
||||||
const pageId = "register.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Register & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
authorizedMailDomains: string[];
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.assign(mock, { authorizedMailDomains });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for my-extra-page-2.ftl", () => {
|
|
||||||
const pageId = "my-extra-page-2.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Common & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
aNonStandardValue2: string;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
kcContext.aNonStandardValue2;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(kcContextCommonMock);
|
|
||||||
|
|
||||||
Object.assign(mock, { pageId, aNonStandardValue2 });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for my-extra-page-1.ftl", () => {
|
|
||||||
const pageId = "my-extra-page-1.ftl";
|
|
||||||
|
|
||||||
console.log("We expect a warning here =>");
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(kcContextCommonMock);
|
|
||||||
|
|
||||||
Object.assign(mock, { pageId });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("returns the proper mock for login.ftl", () => {
|
|
||||||
const pageId = "login.ftl";
|
|
||||||
|
|
||||||
const { getKcContext } = createGetKcContext();
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId: pageId
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login>>();
|
|
||||||
|
|
||||||
assert(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
deepClone(
|
|
||||||
kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("returns undefined when no mock is specified", () => {
|
|
||||||
const { getKcContext } = createGetKcContext();
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext();
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
|
||||||
|
|
||||||
assert(kcContext === undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("mock are properly overwritten", () => {
|
|
||||||
const { getKcContext } = createGetKcContext();
|
|
||||||
|
|
||||||
const displayName = "myDisplayName";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId: "login.ftl",
|
|
||||||
storyPartialKcContext: {
|
|
||||||
realm: {
|
|
||||||
displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login>>();
|
|
||||||
|
|
||||||
assert(kcContext?.realm.displayName === displayName);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("mockPageId doesn't have to be a singleton", () => {
|
|
||||||
const { getKcContext } = createGetKcContext();
|
|
||||||
|
|
||||||
const mockPageId: "login.ftl" | "register.ftl" = "login.ftl" as any;
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login | KcContext.Register>>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("no undefined as long as we provide a mock pageId", () => {
|
|
||||||
const { getKcContext } = createGetKcContext();
|
|
||||||
|
|
||||||
const mockPageId: KcContext["pageId"] = "login.ftl" as any;
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext>>();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,253 +0,0 @@
|
|||||||
import { getKcContext } from "keycloakify/login/kcContext/getKcContext";
|
|
||||||
import type { ExtendKcContext } from "keycloakify/login/kcContext/getKcContextFromWindow";
|
|
||||||
import type { KcContext } from "keycloakify/login/kcContext";
|
|
||||||
import { same } from "evt/tools/inDepth";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import type { Equals } from "tsafe";
|
|
||||||
import {
|
|
||||||
kcContextMocks,
|
|
||||||
kcContextCommonMock
|
|
||||||
} from "keycloakify/login/kcContext/kcContextMocks";
|
|
||||||
import { deepClone } from "keycloakify/tools/deepClone";
|
|
||||||
import { expect, it, describe } from "vitest";
|
|
||||||
|
|
||||||
describe("getKcContext", () => {
|
|
||||||
const authorizedMailDomains = [
|
|
||||||
"example.com",
|
|
||||||
"another-example.com",
|
|
||||||
"*.yet-another-example.com",
|
|
||||||
"*.example.com",
|
|
||||||
"hello-world.com"
|
|
||||||
];
|
|
||||||
|
|
||||||
const displayName = "this is an overwritten common value";
|
|
||||||
|
|
||||||
const aNonStandardValue1 = "a non standard value 1";
|
|
||||||
const aNonStandardValue2 = "a non standard value 2";
|
|
||||||
|
|
||||||
type KcContextExtension =
|
|
||||||
| {
|
|
||||||
pageId: "register.ftl";
|
|
||||||
authorizedMailDomains: string[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "info.ftl";
|
|
||||||
aNonStandardValue1: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "my-extra-page-1.ftl";
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
pageId: "my-extra-page-2.ftl";
|
|
||||||
aNonStandardValue2: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getKcContextProxy = (params: {
|
|
||||||
mockPageId: ExtendKcContext<KcContextExtension>["pageId"];
|
|
||||||
}) => {
|
|
||||||
const { mockPageId } = params;
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext<KcContextExtension>({
|
|
||||||
mockPageId,
|
|
||||||
mockData: [
|
|
||||||
{
|
|
||||||
pageId: "login.ftl",
|
|
||||||
realm: { displayName }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "info.ftl",
|
|
||||||
aNonStandardValue1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "register.ftl",
|
|
||||||
authorizedMailDomains
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pageId: "my-extra-page-2.ftl",
|
|
||||||
aNonStandardValue2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
return { kcContext };
|
|
||||||
};
|
|
||||||
it("has proper API for login.ftl", () => {
|
|
||||||
const pageId = "login.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login>>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
//NOTE: deepClone for printIfExists or other functions...
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
mock.realm.displayName = displayName;
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has a proper API for info.ftl", () => {
|
|
||||||
const pageId = "info.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Info & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
aNonStandardValue1: string;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.assign(mock, { aNonStandardValue1 });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for register.ftl", () => {
|
|
||||||
const pageId = "register.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
//NOTE: I don't understand the need to add: pageId: typeof pageId; ...
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Register & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
authorizedMailDomains: string[];
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(
|
|
||||||
kcContextMocks.find(
|
|
||||||
({ pageId: pageId_i }) => pageId_i === pageId
|
|
||||||
)!
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.assign(mock, { authorizedMailDomains });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for my-extra-page-2.ftl", () => {
|
|
||||||
const pageId = "my-extra-page-2.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<
|
|
||||||
Equals<
|
|
||||||
typeof kcContext,
|
|
||||||
KcContext.Common & {
|
|
||||||
pageId: typeof pageId;
|
|
||||||
aNonStandardValue2: string;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
>();
|
|
||||||
|
|
||||||
kcContext.aNonStandardValue2;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(kcContextCommonMock);
|
|
||||||
|
|
||||||
Object.assign(mock, { pageId, aNonStandardValue2 });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("has a proper API for my-extra-page-1.ftl", () => {
|
|
||||||
const pageId = "my-extra-page-1.ftl";
|
|
||||||
|
|
||||||
console.log("We expect a warning here =>");
|
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ mockPageId: pageId });
|
|
||||||
|
|
||||||
assert(kcContext?.pageId === pageId);
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
(() => {
|
|
||||||
const mock = deepClone(kcContextCommonMock);
|
|
||||||
|
|
||||||
Object.assign(mock, { pageId });
|
|
||||||
|
|
||||||
return mock;
|
|
||||||
})()
|
|
||||||
)
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
it("returns the proper mock for login.ftl", () => {
|
|
||||||
const pageId = "login.ftl";
|
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
|
||||||
mockPageId: pageId
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
|
||||||
|
|
||||||
assert(
|
|
||||||
same(
|
|
||||||
deepClone(kcContext),
|
|
||||||
deepClone(
|
|
||||||
kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("returns undefined when no mock is specified", () => {
|
|
||||||
const { kcContext } = getKcContext();
|
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
|
||||||
|
|
||||||
assert(kcContext === undefined);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,100 +0,0 @@
|
|||||||
import { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import type { Equals } from "tsafe";
|
|
||||||
|
|
||||||
{
|
|
||||||
type Base =
|
|
||||||
| { pageId: "a"; onlyA: string }
|
|
||||||
| { pageId: "b"; onlyB: string }
|
|
||||||
| { pageId: "only base"; onlyBase: string };
|
|
||||||
|
|
||||||
type Extension =
|
|
||||||
| { pageId: "a"; onlyExtA: string }
|
|
||||||
| { pageId: "b"; onlyExtB: string }
|
|
||||||
| { pageId: "only ext"; onlyExt: string };
|
|
||||||
|
|
||||||
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
|
|
||||||
|
|
||||||
type Expected =
|
|
||||||
| { pageId: "a"; onlyA: string; onlyExtA: string }
|
|
||||||
| { pageId: "b"; onlyB: string; onlyExtB: string }
|
|
||||||
| { pageId: "only base"; onlyBase: string }
|
|
||||||
| { pageId: "only ext"; onlyExt: string };
|
|
||||||
|
|
||||||
assert<Equals<Got, Expected>>();
|
|
||||||
|
|
||||||
const x: Got = null as any;
|
|
||||||
|
|
||||||
if (x.pageId === "a") {
|
|
||||||
x.onlyA;
|
|
||||||
x.onlyExtA;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyB;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyBase;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.pageId === "b") {
|
|
||||||
x.onlyB;
|
|
||||||
x.onlyExtB;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyA;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyBase;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.pageId === "only base") {
|
|
||||||
x.onlyBase;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyA;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyB;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyExt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.pageId === "only ext") {
|
|
||||||
x.onlyExt;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyA;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyB;
|
|
||||||
|
|
||||||
//@ts-expect-error
|
|
||||||
x.onlyBase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
type Base =
|
|
||||||
| { pageId: "a"; onlyA: string }
|
|
||||||
| { pageId: "b"; onlyB: string }
|
|
||||||
| { pageId: "only base"; onlyBase: string };
|
|
||||||
|
|
||||||
type Extension = { pageId: "only ext"; onlyExt: string };
|
|
||||||
|
|
||||||
type Got = AndByDiscriminatingKey<"pageId", Extension, Base>;
|
|
||||||
|
|
||||||
type Expected =
|
|
||||||
| { pageId: "a"; onlyA: string }
|
|
||||||
| { pageId: "b"; onlyB: string }
|
|
||||||
| { pageId: "only base"; onlyBase: string }
|
|
||||||
| { pageId: "only ext"; onlyExt: string };
|
|
||||||
|
|
||||||
assert<Equals<Got, Expected>>();
|
|
||||||
}
|
|
211
test/login/kcContext.typelevel-spec.ts
Normal file
211
test/login/kcContext.typelevel-spec.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { type ExtendKcContext, createGetKcContextMock } from "keycloakify/login";
|
||||||
|
import { KcContext as KcContextBase } from "keycloakify/login/kcContext/KcContext";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
import { Reflect } from "tsafe/Reflect";
|
||||||
|
|
||||||
|
{
|
||||||
|
type KcContextExtraProperties = {
|
||||||
|
properties: {
|
||||||
|
myCustomProperty: string | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type KcContextExtraPropertiesPerPage = {
|
||||||
|
"login.ftl": {
|
||||||
|
foo: string;
|
||||||
|
};
|
||||||
|
"my-custom-page.ftl": {
|
||||||
|
bar: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type KcContext = ExtendKcContext<
|
||||||
|
KcContextExtraProperties,
|
||||||
|
KcContextExtraPropertiesPerPage
|
||||||
|
>;
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = Extract<KcContext, { pageId: "login.ftl" }>;
|
||||||
|
type Expected = KcContextBase.Login & {
|
||||||
|
properties: { myCustomProperty: string | undefined };
|
||||||
|
} & { foo: string };
|
||||||
|
|
||||||
|
assert<Equals<Got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = Extract<KcContext, { pageId: "register.ftl" }>;
|
||||||
|
type Expected = KcContextBase.Register & {
|
||||||
|
properties: { myCustomProperty: string | undefined };
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<Equals<Got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = Extract<KcContext, { pageId: "my-custom-page.ftl" }>;
|
||||||
|
|
||||||
|
type Expected = KcContextBase.Common &
|
||||||
|
KcContextExtraProperties & { pageId: "my-custom-page.ftl" } & {
|
||||||
|
properties: { myCustomProperty: string | undefined };
|
||||||
|
} & { bar: number };
|
||||||
|
|
||||||
|
assert<Got extends Expected ? true : false>();
|
||||||
|
assert<Expected extends Got ? true : false>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
|
||||||
|
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>()
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "login.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
|
||||||
|
|
||||||
|
assert<Equals<typeof got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "register.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
|
||||||
|
|
||||||
|
assert<Equals<typeof got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "my-custom-page.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
type Expected = Extract<KcContext, { pageId: "my-custom-page.ftl" }>;
|
||||||
|
|
||||||
|
assert<Equals<typeof got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
getKcContextMock({
|
||||||
|
// @ts-expect-error
|
||||||
|
pageId: "non-existing-page.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
getKcContextMock({
|
||||||
|
pageId: "login.ftl",
|
||||||
|
overrides: {
|
||||||
|
// @ts-expect-error
|
||||||
|
bar: 42
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createGetKcContextMock({
|
||||||
|
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
|
||||||
|
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>(),
|
||||||
|
overrides: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "fr"
|
||||||
|
},
|
||||||
|
// @ts-expect-error
|
||||||
|
profile: {}
|
||||||
|
},
|
||||||
|
overridesPerPage: {
|
||||||
|
"register.ftl": {
|
||||||
|
profile: {
|
||||||
|
attributesByName: {
|
||||||
|
username: {
|
||||||
|
validators: {
|
||||||
|
pattern: {
|
||||||
|
pattern: "^[a-zA-Z0-9]+$",
|
||||||
|
"ignore.empty.value": true,
|
||||||
|
"error-message": "${alphanumericalCharsOnly}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: undefined,
|
||||||
|
name: "username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// @ts-expect-error
|
||||||
|
"non-existing-page.ftl": {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createGetKcContextMock({
|
||||||
|
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
|
||||||
|
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>(),
|
||||||
|
overridesPerPage: {
|
||||||
|
"register.ftl": {
|
||||||
|
// @ts-expect-error
|
||||||
|
nonExistingProperty: 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type KcContextExtraProperties = {};
|
||||||
|
|
||||||
|
type KcContextExtraPropertiesPerPage = {};
|
||||||
|
|
||||||
|
type KcContext = ExtendKcContext<
|
||||||
|
KcContextExtraProperties,
|
||||||
|
KcContextExtraPropertiesPerPage
|
||||||
|
>;
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = Extract<KcContext, { pageId: "login.ftl" }>;
|
||||||
|
type Expected = KcContextBase.Login;
|
||||||
|
|
||||||
|
assert<Equals<Got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = Extract<KcContext, { pageId: "register.ftl" }>;
|
||||||
|
type Expected = KcContextBase.Register;
|
||||||
|
|
||||||
|
assert<Equals<Got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtraProperties: Reflect<KcContextExtraProperties>(),
|
||||||
|
kcContextExtraPropertiesPerPage: Reflect<KcContextExtraPropertiesPerPage>()
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "login.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
|
||||||
|
|
||||||
|
assert<Equals<typeof got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "register.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
|
||||||
|
|
||||||
|
assert<Equals<typeof got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
getKcContextMock({
|
||||||
|
// @ts-expect-error
|
||||||
|
pageId: "non-existing-page.ftl"
|
||||||
|
});
|
||||||
|
|
||||||
|
getKcContextMock({
|
||||||
|
pageId: "login.ftl",
|
||||||
|
overrides: {
|
||||||
|
// @ts-expect-error
|
||||||
|
bar: 42
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
202
test/login/kcContextMock.spec.ts
Normal file
202
test/login/kcContextMock.spec.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import { createGetKcContextMock, type Attribute } from "keycloakify/login";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import {
|
||||||
|
kcContextMocks,
|
||||||
|
kcContextCommonMock
|
||||||
|
} from "keycloakify/login/kcContext/kcContextMocks";
|
||||||
|
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
|
||||||
|
import { expect, it, describe } from "vitest";
|
||||||
|
|
||||||
|
describe("createGetKcContextMock", () => {
|
||||||
|
type KcContextExtraProperties = {
|
||||||
|
properties: {
|
||||||
|
MY_ENV_VAR?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type KcContextExtraPropertiesPerPage = {
|
||||||
|
"register.ftl": {
|
||||||
|
authorizedMailDomains: string[];
|
||||||
|
};
|
||||||
|
"my-plugin-page.ftl": {
|
||||||
|
aCustomValue: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getKcContextMock } = createGetKcContextMock({
|
||||||
|
kcContextExtraProperties: id<KcContextExtraProperties>({
|
||||||
|
properties: {
|
||||||
|
MY_ENV_VAR: "my env var value"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
kcContextExtraPropertiesPerPage: id<KcContextExtraPropertiesPerPage>({
|
||||||
|
"register.ftl": {
|
||||||
|
authorizedMailDomains: ["gmail.com", "hotmail.com"]
|
||||||
|
},
|
||||||
|
"my-plugin-page.ftl": {
|
||||||
|
aCustomValue: "some value"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
overrides: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "fr"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overridesPerPage: {
|
||||||
|
"register.ftl": {
|
||||||
|
profile: {
|
||||||
|
attributesByName: {
|
||||||
|
username: {
|
||||||
|
validators: {
|
||||||
|
pattern: {
|
||||||
|
pattern: "^[a-zA-Z0-9]+$",
|
||||||
|
"ignore.empty.value": true,
|
||||||
|
"error-message": "${alphanumericalCharsOnly}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: undefined,
|
||||||
|
name: "username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
passwordPolicies: {
|
||||||
|
length: 66
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the proper mock for register.frl", () => {
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "register.ftl",
|
||||||
|
overrides: {
|
||||||
|
profile: {
|
||||||
|
attributesByName: {
|
||||||
|
gender: id<Attribute>({
|
||||||
|
validators: {
|
||||||
|
options: {
|
||||||
|
options: [
|
||||||
|
"male",
|
||||||
|
"female",
|
||||||
|
"non-binary",
|
||||||
|
"prefer-not-to-say"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
displayName: "${gender}",
|
||||||
|
annotations: {},
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
name: "gender"
|
||||||
|
}),
|
||||||
|
email: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = (() => {
|
||||||
|
const out: any = structuredCloneButFunctions(
|
||||||
|
kcContextMocks.find(({ pageId }) => pageId === "register.ftl")
|
||||||
|
);
|
||||||
|
|
||||||
|
out.properties = {
|
||||||
|
MY_ENV_VAR: "my env var value"
|
||||||
|
};
|
||||||
|
|
||||||
|
out.authorizedMailDomains = ["gmail.com", "hotmail.com"];
|
||||||
|
|
||||||
|
out.locale.currentLanguageTag = "fr";
|
||||||
|
|
||||||
|
delete out.profile.attributesByName.email;
|
||||||
|
|
||||||
|
{
|
||||||
|
const usernameAttribute = out.profile.attributesByName.username;
|
||||||
|
|
||||||
|
delete usernameAttribute.value;
|
||||||
|
usernameAttribute.validators.pattern = {
|
||||||
|
pattern: "^[a-zA-Z0-9]+$",
|
||||||
|
"ignore.empty.value": true,
|
||||||
|
"error-message": "${alphanumericalCharsOnly}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
out.profile.attributesByName.gender = {
|
||||||
|
validators: {
|
||||||
|
options: {
|
||||||
|
options: ["male", "female", "non-binary", "prefer-not-to-say"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
displayName: "${gender}",
|
||||||
|
annotations: {},
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
name: "gender"
|
||||||
|
};
|
||||||
|
|
||||||
|
(out.passwordPolicies ??= {}).length = 66;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
expect(got).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the proper mock plugin pages", () => {
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "my-plugin-page.ftl",
|
||||||
|
overrides: {
|
||||||
|
locale: {
|
||||||
|
currentLanguageTag: "en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = (() => {
|
||||||
|
const out: any = structuredCloneButFunctions(kcContextCommonMock);
|
||||||
|
|
||||||
|
out.pageId = "my-plugin-page.ftl";
|
||||||
|
|
||||||
|
out.aCustomValue = "some value";
|
||||||
|
|
||||||
|
out.properties = {
|
||||||
|
MY_ENV_VAR: "my env var value"
|
||||||
|
};
|
||||||
|
|
||||||
|
out.locale.currentLanguageTag = "en";
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
expect(got).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the proper mock for other pages", () => {
|
||||||
|
const got = getKcContextMock({
|
||||||
|
pageId: "login.ftl",
|
||||||
|
overrides: {
|
||||||
|
realm: {
|
||||||
|
registrationAllowed: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const expected = (() => {
|
||||||
|
const out: any = structuredCloneButFunctions(
|
||||||
|
kcContextMocks.find(({ pageId }) => pageId === "login.ftl")
|
||||||
|
);
|
||||||
|
|
||||||
|
out.properties = {
|
||||||
|
MY_ENV_VAR: "my env var value"
|
||||||
|
};
|
||||||
|
|
||||||
|
out.locale.currentLanguageTag = "fr";
|
||||||
|
|
||||||
|
out.realm.registrationAllowed = false;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
expect(got).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +0,0 @@
|
|||||||
export function isSameCode(code1: string, code2: string): boolean {
|
|
||||||
const removeSpacesAndNewLines = (code: string) =>
|
|
||||||
code.replace(/\s/g, "").replace(/\n/g, "");
|
|
||||||
|
|
||||||
return removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2);
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user