Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Narrow typing of loadCKEditorCloud result #25

Merged
merged 4 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions src/cdn/loadCKEditorCloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import {
type CKBoxCdnBundlePackConfig
} from './ckbox/createCKBoxCdnBundlePack';

import type { ConditionalBlank } from '../types/ConditionalBlank';
import type { CKCdnVersion } from './ck/createCKCdnUrl';

import {
loadCKCdnResourcesPack,
type InferCKCdnResourcesPackExportsType
Expand Down Expand Up @@ -70,9 +72,9 @@ import {
* Type of plugins can be inferred if `checkPluginLoaded` is not provided: In this case,
* the type of the plugin will be picked from the global window scope.
*/
export function loadCKEditorCloud<Plugins extends CdnPluginsPacks>(
config: CKEditorCloudConfig<Plugins>
): Promise<CKEditorCloudResult<Plugins>> {
export function loadCKEditorCloud<Config extends CKEditorCloudConfig>(
config: Config
): Promise<CKEditorCloudResult<Config>> {
const {
version, languages, plugins,
premium, ckbox
Expand All @@ -98,34 +100,42 @@ export function loadCKEditorCloud<Plugins extends CdnPluginsPacks>(
loadedPlugins: combineCdnPluginsPacks( plugins ?? {} )
} );

return loadCKCdnResourcesPack( pack ) as Promise<CKEditorCloudResult<Plugins>>;
return loadCKCdnResourcesPack( pack ) as Promise<CKEditorCloudResult<Config>>;
}

/**
* The result of the resolved bundles from CKEditor Cloud Services.
*/
export type CKEditorCloudResult<Plugins extends CdnPluginsPacks = any> = {
export type CKEditorCloudResult<Config extends CKEditorCloudConfig> = {

/**
* The base CKEditor bundle exports.
*/
CKEditor: NonNullable<Window['CKEDITOR']>;

/**
* The CKEditor Premium Features bundle exports.
* The CKBox bundle exports.
* It's available only if the `ckbox` configuration is provided.
*/
CKEditorPremiumFeatures?: Window['CKEDITOR_PREMIUM_FEATURES'];
CKBox: ConditionalBlank<
Config['ckbox'],
Window['CKBox']
>;

/**
* The CKBox bundle exports.
* The CKEditor Premium Features bundle exports.
* It's available only if the `premium` configuration is provided.
*/
CKBox?: Window['CKBox'];
CKEditorPremiumFeatures: ConditionalBlank<
Config['premium'],
Window['CKEDITOR_PREMIUM_FEATURES']
>;

/**
* The additional resources exports.
*/
loadedPlugins: InferCKCdnResourcesPackExportsType<
CombinedPluginsPackWithFallbackScope<Plugins>
CombinedPluginsPackWithFallbackScope<NonNullable<Config['plugins']>>
>;
};

Expand Down
13 changes: 13 additions & 0 deletions src/types/ConditionalBlank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* A type that allows to set a value or leave it blank conditionally.
* It's useful when we provide config, and depending on the config, we want to set a value or leave it blank.
*/
export type ConditionalBlank<Condition, Value> =
Condition extends true | object ? NonNullable<Value> :
Condition extends boolean ? NonNullable<Value> | undefined :
undefined;
47 changes: 46 additions & 1 deletion tests/cdn/loadCKEditorCloud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
expectTypeOf, beforeEach, afterEach
} from 'vitest';

import type { AIAdapter } from 'ckeditor5-premium-features';

import { loadCKEditorCloud } from '@/cdn/loadCKEditorCloud';
import { createCKBoxBundlePack } from '@/cdn/ckbox/createCKBoxCdnBundlePack';
import { removeCkCdnLinks, removeCkCdnScripts } from 'tests/_utils/ckCdnMocks';
Expand Down Expand Up @@ -76,7 +78,7 @@ describe( 'loadCKEditorCloud', () => {
expect( loadedPlugins?.Plugin ).toBeDefined();
} );

describe( 'plugins', () => {
describe( 'typings', () => {
it( 'should properly infer type of global variable if checkPluginLoaded is not provided', async () => {
const { loadedPlugins } = await loadCKEditorCloud( {
version: '43.0.0',
Expand All @@ -101,6 +103,49 @@ describe( 'loadCKEditorCloud', () => {

expectTypeOf( loadedPlugins.FakePlugin ).toEqualTypeOf<FakePlugin2>();
} );

it( 'should set CKBox result type as non-nullable if ckbox config is provided', async () => {
const { CKBox } = await loadCKEditorCloud( {
version: '43.0.0',
ckbox: {
version: '2.5.1'
}
} );

expectTypeOf( CKBox ).not.toBeNullable();
} );

it( 'should set CKBox result type as nullable if ckbox config is not provided', async () => {
const { CKBox } = await loadCKEditorCloud( {
version: '43.0.0'
} );

expectTypeOf( CKBox ).toBeUndefined();
} );

it( 'should set CKEditorPremiumFeatures type as non-nullable if premium=true config is provided', async () => {
const { CKEditorPremiumFeatures } = await loadCKEditorCloud( {
version: '43.0.0',
premium: true,
ckbox: {
version: '2.5.1'
}
} );

expectTypeOf( CKEditorPremiumFeatures ).not.toBeNullable();
expectTypeOf( CKEditorPremiumFeatures.AIAdapter ).toEqualTypeOf<typeof AIAdapter>();
} );

it( 'should set CKEditorPremiumFeatures type as nullable if premium=true config is not provided', async () => {
const { CKEditorPremiumFeatures } = await loadCKEditorCloud( {
version: '43.0.0',
ckbox: {
version: '2.5.1'
}
} );

expectTypeOf( CKEditorPremiumFeatures ).toBeUndefined();
} );
} );
} );

Expand Down