Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { UmbApi } from '../models/api.interface.js';
import { loadManifestApi } from './load-manifest-api.function.js';
import { expect } from '@open-wc/testing';

class UmbTestApiTrue implements UmbApi {
isValidClassInstance() {
return true;
}
destroy() {}
}

class UmbTestApiFalse implements UmbApi {
isValidClassInstance() {
return false;
}
destroy() {}
}

const jsModuleWithDefaultExport = {
default: UmbTestApiTrue,
};

const jsModuleWithApiExport = {
api: UmbTestApiTrue,
};

const jsModuleWithDefaultAndApiExport = {
default: UmbTestApiFalse,
api: UmbTestApiTrue,
};

describe('loadManifestApi', () => {
describe('class constructor', () => {
it('returns the class constructor when passed directly', async () => {
const result = await loadManifestApi(UmbTestApiTrue);
expect(result).to.equal(UmbTestApiTrue);
});
});

describe('dynamic import (function)', () => {
it('returns the api class from default export', async () => {
const result = await loadManifestApi(() => Promise.resolve(jsModuleWithDefaultExport));
expect(result).to.equal(UmbTestApiTrue);
});

it('returns the api class from api export', async () => {
const result = await loadManifestApi(() => Promise.resolve(jsModuleWithApiExport));
expect(result).to.equal(UmbTestApiTrue);
});

it('prioritizes api export over default export', async () => {
const result = await loadManifestApi(() => Promise.resolve(jsModuleWithDefaultAndApiExport));
expect(result).to.equal(UmbTestApiTrue);
});

it('returns undefined when loader returns null', async () => {
const result = await loadManifestApi(() => Promise.resolve(null as any));
expect(result).to.be.undefined;
});

it('returns undefined when loader returns object without valid exports', async () => {
const result = await loadManifestApi(() => Promise.resolve({ other: 'value' } as any));
expect(result).to.be.undefined;
});

it('returns undefined when export is not a function', async () => {
const result = await loadManifestApi(() => Promise.resolve({ default: 'not-a-function' } as any));
expect(result).to.be.undefined;
});
});

describe('static import (object)', () => {
it('returns the api class from default export', async () => {
const result = await loadManifestApi(jsModuleWithDefaultExport);
expect(result).to.equal(UmbTestApiTrue);
});

it('returns the api class from api export', async () => {
const result = await loadManifestApi(jsModuleWithApiExport);
expect(result).to.equal(UmbTestApiTrue);
});

it('prioritizes api export over default export', async () => {
const result = await loadManifestApi(jsModuleWithDefaultAndApiExport);
expect(result).to.equal(UmbTestApiTrue);
});

it('returns undefined when object has no valid exports', async () => {
const result = await loadManifestApi({ other: 'value' } as any);
expect(result).to.be.undefined;
});

it('returns undefined when export is not a function', async () => {
const result = await loadManifestApi({ default: 'not-a-function' } as any);
expect(result).to.be.undefined;
});
});

describe('edge cases', () => {
it('returns undefined when passed null', async () => {
const result = await loadManifestApi(null as any);
expect(result).to.be.undefined;
});

it('returns undefined when passed undefined', async () => {
const result = await loadManifestApi(undefined as any);
expect(result).to.be.undefined;
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { UmbApi } from '../models/api.interface.js';
import type {
ApiLoaderExports,
ApiLoaderPromise,
ApiLoaderProperty,
ClassConstructor,
ElementAndApiLoaderProperty,
Expand All @@ -20,10 +21,8 @@ export async function loadManifestApi<ApiType extends UmbApi>(
// Class Constructor
return property as ClassConstructor<ApiType>;
} else {
// Promise function
const result = await (
property as Exclude<Exclude<ApiLoaderProperty<ApiType>, string>, ClassConstructor<ApiType>>
)();
// Promise function (dynamic import)
const result = await (property as ApiLoaderPromise<ApiType>)();
if (typeof result === 'object' && result != null) {
const exportValue =
('api' in result ? result.api : undefined) ||
Expand All @@ -43,6 +42,14 @@ export async function loadManifestApi<ApiType extends UmbApi>(
return exportValue;
}
}
} else if (propType === 'object' && property !== null) {
// Already resolved module object (statically imported)
const result = property as ApiLoaderExports<ApiType>;
const exportValue =
('api' in result ? result.api : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}
return undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { loadManifestElement } from './load-manifest-element.function.js';
import { expect } from '@open-wc/testing';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

@customElement('umb-test-element-true')
class UmbTestElementTrue extends UmbLitElement {
isValidClassInstance() {
return true;
}
}

@customElement('umb-test-element-false')
class UmbTestElementFalse extends UmbLitElement {
isValidClassInstance() {
return false;
}
}

const jsModuleWithDefaultExport = {
default: UmbTestElementTrue,
};

const jsModuleWithElementExport = {
element: UmbTestElementTrue,
};

const jsModuleWithDefaultAndElementExport = {
default: UmbTestElementFalse,
element: UmbTestElementTrue,
};

describe('loadManifestElement', () => {
describe('class constructor', () => {
it('returns the class constructor when passed directly', async () => {
const result = await loadManifestElement(UmbTestElementTrue);
expect(result).to.equal(UmbTestElementTrue);
});
});

describe('dynamic import (function)', () => {
it('returns the element class from default export', async () => {
const result = await loadManifestElement(() => Promise.resolve(jsModuleWithDefaultExport));
expect(result).to.equal(UmbTestElementTrue);
});

it('returns the element class from element export', async () => {
const result = await loadManifestElement(() => Promise.resolve(jsModuleWithElementExport));
expect(result).to.equal(UmbTestElementTrue);
});

it('prioritizes element export over default export', async () => {
const result = await loadManifestElement(() => Promise.resolve(jsModuleWithDefaultAndElementExport));
expect(result).to.equal(UmbTestElementTrue);
});

it('returns undefined when loader returns null', async () => {
const result = await loadManifestElement(() => Promise.resolve(null as any));
expect(result).to.be.undefined;
});

it('returns undefined when loader returns object without valid exports', async () => {
const result = await loadManifestElement(() => Promise.resolve({ other: 'value' } as any));
expect(result).to.be.undefined;
});

it('returns undefined when export is not a function', async () => {
const result = await loadManifestElement(() => Promise.resolve({ default: 'not-a-function' } as any));
expect(result).to.be.undefined;
});
});

describe('static import (object)', () => {
it('returns the element class from default export', async () => {
const result = await loadManifestElement(jsModuleWithDefaultExport);
expect(result).to.equal(UmbTestElementTrue);
});

it('returns the element class from element export', async () => {
const result = await loadManifestElement(jsModuleWithElementExport);
expect(result).to.equal(UmbTestElementTrue);
});

it('prioritizes element export over default export', async () => {
const result = await loadManifestElement(jsModuleWithDefaultAndElementExport);
expect(result).to.equal(UmbTestElementTrue);
});

it('returns undefined when object has no valid exports', async () => {
const result = await loadManifestElement({ other: 'value' } as any);
expect(result).to.be.undefined;
});

it('returns undefined when export is not a function', async () => {
const result = await loadManifestElement({ default: 'not-a-function' } as any);
expect(result).to.be.undefined;
});
});

describe('edge cases', () => {
it('returns undefined when passed null', async () => {
const result = await loadManifestElement(null as any);
expect(result).to.be.undefined;
});

it('returns undefined when passed undefined', async () => {
const result = await loadManifestElement(undefined as any);
expect(result).to.be.undefined;
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
ClassConstructor,
ElementAndApiLoaderProperty,
ElementLoaderExports,
ElementLoaderPromise,
ElementLoaderProperty,
} from '../types/utils.js';

Expand All @@ -19,8 +20,8 @@ export async function loadManifestElement<ElementType extends HTMLElement>(
// Class Constructor
return property as ClassConstructor<ElementType>;
} else {
// Promise function
const result = await (property as Exclude<Exclude<typeof property, string>, ClassConstructor<ElementType>>)();
// Promise function (dynamic import)
const result = await (property as ElementLoaderPromise<ElementType>)();
if (typeof result === 'object' && result !== null) {
const exportValue =
('element' in result ? result.element : undefined) ||
Expand All @@ -42,6 +43,14 @@ export async function loadManifestElement<ElementType extends HTMLElement>(
return exportValue;
}
}
} else if (propType === 'object' && property !== null) {
// Already resolved module object (statically imported)
const result = property as ElementLoaderExports<ElementType>;
const exportValue =
('element' in result ? result.element : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}
return undefined;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { loadManifestPlainJs } from './load-manifest-plain-js.function.js';
import { expect } from '@open-wc/testing';

interface TestModule {
value: string;
getValue(): string;
}

const testModuleObject: TestModule = {
value: 'test-value',
getValue() {
return this.value;
},
};

const jsModuleWithTestExport = {
value: 'dynamic-import-value',
getValue() {
return this.value;
},
};

describe('loadManifestPlainJs', () => {
describe('dynamic import (function)', () => {
it('returns the module when loader returns an object', async () => {
const result = await loadManifestPlainJs<TestModule>(() => Promise.resolve(jsModuleWithTestExport));
expect(result).to.not.be.undefined;
expect(result?.value).to.equal('dynamic-import-value');
expect(result?.getValue()).to.equal('dynamic-import-value');
});

it('returns undefined when loader returns null', async () => {
const result = await loadManifestPlainJs<TestModule>(() => Promise.resolve(null as unknown as TestModule));
expect(result).to.be.undefined;
});

it('returns undefined when loader returns a primitive', async () => {
const result = await loadManifestPlainJs<TestModule>(() => Promise.resolve('string' as unknown as TestModule));
expect(result).to.be.undefined;
});
});

describe('static import (object)', () => {
it('returns the module object directly when passed an object', async () => {
const result = await loadManifestPlainJs<TestModule>(testModuleObject);
expect(result).to.not.be.undefined;
expect(result?.value).to.equal('test-value');
expect(result?.getValue()).to.equal('test-value');
});

it('returns undefined when passed null', async () => {
const result = await loadManifestPlainJs<TestModule>(null as unknown as TestModule);
expect(result).to.be.undefined;
});
});

describe('edge cases', () => {
it('returns undefined when passed undefined', async () => {
const result = await loadManifestPlainJs<TestModule>(undefined as unknown as TestModule);
expect(result).to.be.undefined;
});
});
});
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import type { JsLoaderProperty } from '../types/utils.js';
import type { JsLoaderPromise } from '../types/utils.js';

/**
*
* @param property
*/
export async function loadManifestPlainJs<JsType>(property: JsLoaderProperty<JsType>): Promise<JsType | undefined> {
const propType = typeof property;
if (propType === 'function') {
// Promise function
const result = await (property as Exclude<typeof property, string>)();
export async function loadManifestPlainJs<JsType>(
Comment thread
nielslyngsoe marked this conversation as resolved.
property: string | JsLoaderPromise<JsType> | JsType,
): Promise<JsType | undefined> {
if (typeof property === 'function') {
// Promise function (dynamic import)
const result = await (property as JsLoaderPromise<JsType>)();
if (typeof result === 'object' && result != null) {
return result;
}
} else if (propType === 'string') {
} else if (typeof property === 'string') {
// Import string
const result = await (import(/* @vite-ignore */ property as string) as unknown as JsType);
const result = await (import(/* @vite-ignore */ property) as unknown as JsType);
if (typeof result === 'object' && result != null) {
return result;
}
} else if (typeof property === 'object' && property != null) {
// Already resolved module object (statically imported)
return property;

Check warning on line 24 in src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Complex Method

loadManifestPlainJs has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}
return undefined;
}
Loading
Loading