mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-19 16:55:02 +02:00
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.18.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (26.5.1) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Verify single-instance deps (npm-install) (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
127 lines
3.8 KiB
TypeScript
127 lines
3.8 KiB
TypeScript
import { describe, it, expect, expectTypeOf } from 'vitest';
|
|
|
|
import { defineFrontendModule } from './defineFrontendModule';
|
|
import type { FrontendModuleDescription } from './types/descriptor';
|
|
|
|
describe('defineFrontendModule', () => {
|
|
it('should return the same object it was given', () => {
|
|
const descriptor = {
|
|
id: 'identity',
|
|
name: 'Identity',
|
|
description: 'A descriptor passed straight through',
|
|
icon: 'box',
|
|
};
|
|
|
|
expect(defineFrontendModule(descriptor)).toBe(descriptor);
|
|
});
|
|
|
|
it('should not freeze, seal, or clone the descriptor', () => {
|
|
const module: FrontendModuleDescription = defineFrontendModule({
|
|
id: 'mutable',
|
|
name: 'Mutable',
|
|
description: 'A descriptor that stays writable',
|
|
icon: 'box',
|
|
resources: [{ key: 'mutable', displayName: 'Mutable' }],
|
|
});
|
|
|
|
expect(Object.isFrozen(module)).toBe(false);
|
|
expect(Object.isSealed(module)).toBe(false);
|
|
expect(Object.isExtensible(module)).toBe(true);
|
|
|
|
// The shell reads a descriptor through the contract type, and registration
|
|
// order lets a host append. Nothing the helper does may prevent that.
|
|
module.resources?.push({ key: 'added', displayName: 'Added' });
|
|
expect(module.resources).toHaveLength(2);
|
|
});
|
|
|
|
it('should keep a getter lazy instead of reading it', () => {
|
|
let reads = 0;
|
|
const module = defineFrontendModule({
|
|
id: 'lazy',
|
|
name: 'Lazy',
|
|
description: 'A descriptor with a getter-backed field',
|
|
icon: 'box',
|
|
get settingsPages() {
|
|
reads += 1;
|
|
return [];
|
|
},
|
|
});
|
|
|
|
expect(reads).toBe(0);
|
|
expect(module.settingsPages).toHaveLength(0);
|
|
expect(reads).toBe(1);
|
|
});
|
|
|
|
it('should keep the literal type of each field', () => {
|
|
const module = defineFrontendModule({
|
|
id: 'literal',
|
|
name: 'Literal',
|
|
description: 'A descriptor whose literal types survive',
|
|
icon: 'box',
|
|
});
|
|
|
|
expectTypeOf(module.id).toEqualTypeOf<'literal'>();
|
|
expectTypeOf(module.name).toEqualTypeOf<'Literal'>();
|
|
expectTypeOf(module.icon).toEqualTypeOf<'box'>();
|
|
});
|
|
|
|
it('should narrow optional fields a descriptor declares', () => {
|
|
const module = defineFrontendModule({
|
|
id: 'narrow',
|
|
name: 'Narrow',
|
|
description: 'A descriptor declaring one optional surface',
|
|
icon: 'box',
|
|
commands: [{ id: 'narrow.open', title: 'Open Narrow' }],
|
|
});
|
|
|
|
// An annotated descriptor widens this to `CommandBarEntry[] | undefined`.
|
|
expectTypeOf(module.commands).not.toBeUndefined();
|
|
expectTypeOf(module).toExtend<FrontendModuleDescription>();
|
|
});
|
|
|
|
it('should type-check a descriptor against the contract', () => {
|
|
// @ts-expect-error `id` is required.
|
|
defineFrontendModule({ name: 'No id', description: '', icon: 'box' });
|
|
|
|
// @ts-expect-error `icon` must be a string.
|
|
defineFrontendModule({ id: 'typed', name: 'Typed', description: '', icon: 42 });
|
|
|
|
// @ts-expect-error `routes` must hold route records.
|
|
defineFrontendModule({ id: 'typed', name: 'Typed', description: '', icon: 'box', routes: [1] });
|
|
});
|
|
|
|
it('should keep array element types narrow under `const` inference', () => {
|
|
const module = defineFrontendModule({
|
|
id: 'narrow-elements',
|
|
name: 'Narrow elements',
|
|
description: 'A descriptor whose array elements keep their literal types',
|
|
icon: 'box',
|
|
resources: [{ key: 'thing', displayName: 'Thing' }],
|
|
});
|
|
|
|
expectTypeOf(module.resources[0].key).toEqualTypeOf<'thing'>();
|
|
expect(module.resources[0].key).toBe('thing');
|
|
});
|
|
|
|
it('should type a route guard from the contract', () => {
|
|
const module = defineFrontendModule({
|
|
id: 'guarded',
|
|
name: 'Guarded',
|
|
description: 'A descriptor whose guard parameter is typed by the contract',
|
|
icon: 'box',
|
|
routes: [
|
|
{
|
|
path: '/guarded',
|
|
component: async () => await Promise.resolve({}),
|
|
beforeEnter: (to) => {
|
|
expectTypeOf(to.params).not.toBeAny();
|
|
return true;
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(module.routes).toHaveLength(1);
|
|
});
|
|
});
|