@linagora/linid-im-front-corelib 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +3 -3
  2. package/dist/core-lib.es.js +923 -852
  3. package/dist/core-lib.umd.js +9 -9
  4. package/dist/package.json +19 -14
  5. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  6. package/dist/types/src/index.d.ts +4 -0
  7. package/dist/types/src/services/httpClientService.d.ts +13 -0
  8. package/dist/types/src/services/linIdConfigurationService.d.ts +21 -0
  9. package/dist/types/src/stores/linIdConfigurationStore.d.ts +79 -0
  10. package/dist/types/src/types/linidConfiguration.d.ts +42 -0
  11. package/package.json +18 -13
  12. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -83
  13. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -90
  14. package/.github/ISSUE_TEMPLATE/question.yml +0 -31
  15. package/.github/ISSUE_TEMPLATE/security.yml +0 -69
  16. package/.github/actions/setup-node-pnpm/action.yml +0 -29
  17. package/.github/workflows/pull-request.yml +0 -147
  18. package/.github/workflows/release.yml +0 -90
  19. package/.prettierignore +0 -7
  20. package/.prettierrc.json +0 -5
  21. package/.vscode/extensions.json +0 -3
  22. package/.vscode/settings.json +0 -9
  23. package/CHANGELOG.md +0 -40
  24. package/CONTRIBUTING.md +0 -269
  25. package/COPYRIGHT +0 -23
  26. package/docs/components-plugin-zones.md +0 -168
  27. package/docs/helpers.md +0 -188
  28. package/docs/module-lifecycle.md +0 -717
  29. package/docs/types-and-interfaces.md +0 -92
  30. package/eslint.config.js +0 -136
  31. package/src/components/LinidZoneRenderer.vue +0 -77
  32. package/src/index.ts +0 -51
  33. package/src/lifecycle/skeleton.ts +0 -147
  34. package/src/services/federationService.ts +0 -44
  35. package/src/stores/linidZoneStore.ts +0 -62
  36. package/src/types/linidZone.ts +0 -48
  37. package/src/types/module.ts +0 -96
  38. package/src/types/moduleLifecycle.ts +0 -154
  39. package/tests/unit/components/LinidZoneRenderer.spec.js +0 -135
  40. package/tests/unit/lifecycle/skeleton.spec.js +0 -138
  41. package/tests/unit/services/federationService.spec.js +0 -146
  42. package/tests/unit/stores/linidZoneStore.spec.js +0 -94
  43. package/tsconfig.json +0 -14
  44. package/tsconfig.lib.json +0 -20
  45. package/tsconfig.node.json +0 -9
  46. package/tsconfig.spec.json +0 -16
  47. package/vite.config.ts +0 -37
  48. package/vitest.config.ts +0 -19
@@ -1,96 +0,0 @@
1
- /*
2
- * Copyright (C) 2025 Linagora
3
- *
4
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
5
- * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
6
- * any later version, provided you comply with the Additional Terms applicable for LinID Identity Manager software by
7
- * LINAGORA pursuant to Section 7 of the GNU Affero General Public License, subsections (b), (c), and (e), pursuant to
8
- * which these Appropriate Legal Notices must notably (i) retain the display of the "LinID™" trademark/logo at the top
9
- * of the interface window, the display of the “You are using the Open Source and free version of LinID™, powered by
10
- * Linagora © 2009–2013. Contribute to LinID R&D by subscribing to an Enterprise offer!” infobox and in the e-mails
11
- * sent with the Program, notice appended to any type of outbound messages (e.g. e-mail and meeting requests) as well
12
- * as in the LinID Identity Manager user interface, (ii) retain all hypertext links between LinID Identity Manager
13
- * and https://linid.org/, as well as between LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA
14
- * intellectual property rights over its trademarks and commercial brands. Other Additional Terms apply, see
15
- * <http://www.linagora.com/licenses/> for more details.
16
- *
17
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
18
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19
- * details.
20
- *
21
- * You should have received a copy of the GNU Affero General Public License and its applicable Additional Terms for
22
- * LinID Identity Manager along with this program. If not, see <http://www.gnu.org/licenses/> for the GNU Affero
23
- * General Public License version 3 and <http://www.linagora.com/licenses/> for the Additional Terms applicable to the
24
- * LinID Identity Manager software.
25
- */
26
-
27
- import type { Component } from 'vue';
28
- import type { ModuleLifecycleHooks } from './moduleLifecycle';
29
-
30
- /**
31
- * Remote module interface.
32
- *
33
- * All remote modules exposed via Module Federation should implement this interface.
34
- * This is the contract between the host application and remote modules.
35
- */
36
- export interface RemoteModule extends ModuleLifecycleHooks {
37
- /**
38
- * Unique identifier for the module.
39
- *
40
- * Should be in kebab-case and match the ID in the module configuration.
41
- */
42
- id: string;
43
-
44
- /**
45
- * Human-readable name of the module.
46
- */
47
- name: string;
48
-
49
- /**
50
- * Version of the module.
51
- *
52
- * Should follow semantic versioning (semver).
53
- */
54
- version: string;
55
-
56
- /**
57
- * Optional description of the module.
58
- *
59
- * Provide a brief description of what the module does.
60
- */
61
- description?: string;
62
- }
63
-
64
- /**
65
- * Module configuration in the host (module-<name>.json).
66
- *
67
- * This is what the host provides to each module during the configuration phase.
68
- * The host reads this from `module-<name>.json` files.
69
- */
70
- export interface ModuleHostConfig {
71
- /**
72
- * Unique module identifier (kebab-case).
73
- *
74
- * Must match the module's exported `id` field.
75
- */
76
- id: string;
77
-
78
- /**
79
- * Module Federation remote name (must match a key in remotes.json).
80
- *
81
- * This is the name used to load the remote module via Module Federation.
82
- */
83
- remoteName: string;
84
- }
85
-
86
- /**
87
- * Module structure for a Vue component exposed via Module Federation.
88
- *
89
- * Remote modules must export a Vue component as their default export.
90
- */
91
- export interface RemoteComponentModule {
92
- /**
93
- * The default exported Vue component.
94
- */
95
- default: Component;
96
- }
@@ -1,154 +0,0 @@
1
- /*
2
- * Copyright (C) 2025 Linagora
3
- *
4
- * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
5
- * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
6
- * any later version, provided you comply with the Additional Terms applicable for LinID Identity Manager software by
7
- * LINAGORA pursuant to Section 7 of the GNU Affero General Public License, subsections (b), (c), and (e), pursuant to
8
- * which these Appropriate Legal Notices must notably (i) retain the display of the "LinID™" trademark/logo at the top
9
- * of the interface window, the display of the “You are using the Open Source and free version of LinID™, powered by
10
- * Linagora © 2009–2013. Contribute to LinID R&D by subscribing to an Enterprise offer!” infobox and in the e-mails
11
- * sent with the Program, notice appended to any type of outbound messages (e.g. e-mail and meeting requests) as well
12
- * as in the LinID Identity Manager user interface, (ii) retain all hypertext links between LinID Identity Manager
13
- * and https://linid.org/, as well as between LINAGORA and LINAGORA.com, and (iii) refrain from infringing LINAGORA
14
- * intellectual property rights over its trademarks and commercial brands. Other Additional Terms apply, see
15
- * <http://www.linagora.com/licenses/> for more details.
16
- *
17
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
18
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
19
- * details.
20
- *
21
- * You should have received a copy of the GNU Affero General Public License and its applicable Additional Terms for
22
- * LinID Identity Manager along with this program. If not, see <http://www.gnu.org/licenses/> for the GNU Affero
23
- * General Public License version 3 and <http://www.linagora.com/licenses/> for the Additional Terms applicable to the
24
- * LinID Identity Manager software.
25
- */
26
-
27
- import type { ModuleHostConfig } from './module';
28
-
29
- /**
30
- * Module lifecycle phases enumeration.
31
- *
32
- * Defines the sequence of initialization steps for remote modules.
33
- * Each phase is executed in order for all modules before moving to the next phase.
34
- */
35
- export enum ModuleLifecyclePhase {
36
- /**
37
- * Initial setup phase - module is being loaded.
38
- *
39
- * Use this phase to validate dependencies and prepare the module.
40
- */
41
- SETUP = 'setup',
42
-
43
- /**
44
- * Configuration phase - module receives configuration.
45
- *
46
- * Use this phase to receive and validate the host configuration.
47
- */
48
- CONFIGURE = 'configure',
49
-
50
- /**
51
- * Initialization phase - module initializes its core features.
52
- *
53
- * Use this phase to register stores and initialize resources.
54
- */
55
- INITIALIZE = 'initialize',
56
-
57
- /**
58
- * Ready phase - module is ready to be used.
59
- *
60
- * Use this phase to perform final checks and emit ready state.
61
- */
62
- READY = 'ready',
63
-
64
- /**
65
- * Post-initialization phase - final setup after all modules are ready.
66
- *
67
- * Use this phase for cross-module integrations and final setup.
68
- */
69
- POST_INIT = 'postInit',
70
- }
71
-
72
- /**
73
- * Result of a lifecycle phase execution.
74
- *
75
- * Modules should return this from their lifecycle hooks to indicate
76
- * success or failure of the phase.
77
- */
78
- export interface ModuleLifecycleResult {
79
- /**
80
- * Whether the phase completed successfully.
81
- *
82
- * If false, the module will continue through remaining phases but
83
- * the error will be logged.
84
- */
85
- success: boolean;
86
-
87
- /**
88
- * Error message if the phase failed.
89
- *
90
- * Only present when success is false.
91
- */
92
- error?: string;
93
-
94
- /**
95
- * Additional metadata from the phase.
96
- *
97
- * Use this to provide debugging information or statistics.
98
- */
99
- metadata?: Record<string, unknown>;
100
- }
101
-
102
- /**
103
- * Module lifecycle hooks interface.
104
- *
105
- * Remote modules should implement these hooks to participate in the lifecycle.
106
- * All hooks are optional - implement only what your module needs.
107
- */
108
- export interface ModuleLifecycleHooks {
109
- /**
110
- * Called when the module is first loaded.
111
- *
112
- * Use this to prepare the module for initialization and validate
113
- * that all required dependencies are available.
114
- * @returns Promise resolving to the lifecycle result.
115
- */
116
- setup(): Promise<ModuleLifecycleResult>;
117
-
118
- /**
119
- * Called to configure the module with application-specific settings.
120
- *
121
- * Use this to receive and validate the host configuration for your module.
122
- * This is where you should check that all required configuration is present.
123
- * @param config - Module-specific configuration from host (from module-<name>.json).
124
- * @returns Promise resolving to the lifecycle result.
125
- */
126
- configure(config: ModuleHostConfig): Promise<ModuleLifecycleResult>;
127
-
128
- /**
129
- * Called to initialize the module's core functionality.
130
- *
131
- * Use this to register Pinia stores and initialize any resources
132
- * your module needs to function.
133
- * @returns Promise resolving to the lifecycle result.
134
- */
135
- initialize(): Promise<ModuleLifecycleResult>;
136
-
137
- /**
138
- * Called when the module is ready to be used.
139
- *
140
- * Use this to perform final checks and emit ready state.
141
- * At this point, all other modules have completed initialization.
142
- * @returns Promise resolving to the lifecycle result.
143
- */
144
- ready(): Promise<ModuleLifecycleResult>;
145
-
146
- /**
147
- * Called after all modules have been initialized.
148
- *
149
- * Use this for cross-module integrations and final setup that requires
150
- * all modules to be ready.
151
- * @returns Promise resolving to the lifecycle result.
152
- */
153
- postInit(): Promise<ModuleLifecycleResult>;
154
- }
@@ -1,135 +0,0 @@
1
- import { flushPromises, shallowMount } from '@vue/test-utils';
2
- import { createPinia, setActivePinia } from 'pinia';
3
- import LinidZoneRenderer from 'src/components/LinidZoneRenderer.vue';
4
- import * as federationService from 'src/services/federationService';
5
- import { useLinidZoneStore } from 'src/stores/linidZoneStore';
6
- import { beforeEach, describe, expect, it, vi } from 'vitest';
7
- import { nextTick, watch } from 'vue';
8
-
9
- vi.mock('src/services/federationService', () => ({
10
- loadAsyncComponent: vi.fn(),
11
- }));
12
-
13
- describe('Test component: LinidZoneRenderer', () => {
14
- let pinia;
15
- let store;
16
- let wrapper;
17
-
18
- beforeEach(() => {
19
- pinia = createPinia();
20
- setActivePinia(pinia);
21
- store = useLinidZoneStore();
22
- wrapper = shallowMount(LinidZoneRenderer, {
23
- props: { zone: '' },
24
- global: { plugins: [pinia] },
25
- });
26
- vi.clearAllMocks();
27
- });
28
-
29
- describe('Test watchEffect', () => {
30
- it('should initialize isLoadingComplete to false when mounted', async () => {
31
- const values = [];
32
- const stopWatch = watch(
33
- () => wrapper.vm.isLoadingComplete,
34
- (newVal) => {
35
- values.push(newVal);
36
- },
37
- { flush: 'sync' }
38
- );
39
-
40
- await wrapper.setProps({ zone: 'zone-test' });
41
-
42
- await flushPromises();
43
- await nextTick();
44
- stopWatch();
45
-
46
- expect(values).toEqual([false, true]);
47
- });
48
-
49
- it('should set isLoadingComplete to true after initialization', async () => {
50
- wrapper.setProps({ zone: 'any-zone' });
51
-
52
- await flushPromises();
53
- await nextTick();
54
-
55
- expect(wrapper.vm.isLoadingComplete).toBe(true);
56
- });
57
-
58
- it('should not call loadAsyncComponent when the zone is not registered in store', async () => {
59
- wrapper.setProps({ zone: 'unregistered-zone' });
60
-
61
- await flushPromises();
62
- await nextTick();
63
-
64
- expect(federationService.loadAsyncComponent).not.toHaveBeenCalled();
65
- expect(wrapper.vm.components).toEqual([]);
66
- });
67
-
68
- it('should not call loadAsyncComponent when the zone is registered has no entries', async () => {
69
- wrapper.vm.linidZoneStore.zones['empty-zone'] = [];
70
-
71
- wrapper.setProps({ zone: 'empty-zone' });
72
-
73
- await flushPromises();
74
- await nextTick();
75
-
76
- expect(federationService.loadAsyncComponent).not.toHaveBeenCalled();
77
- expect(wrapper.vm.components).toEqual([]);
78
- });
79
-
80
- it('should load components from store when zone is registered and has entries', async () => {
81
- const MockComponentA = {
82
- name: 'MockComponent',
83
- template: '<div>Mock</div>',
84
- };
85
- const MockComponentB = {
86
- name: 'MockComponent',
87
- template: '<div>Mock</div>',
88
- };
89
- vi.mocked(federationService.loadAsyncComponent)
90
- .mockReturnValueOnce(MockComponentA)
91
- .mockReturnValueOnce(MockComponentB);
92
-
93
- store.register('test-zone', {
94
- plugin: 'test-plugin/MockComponentA',
95
- props: { title: 'Test' },
96
- });
97
-
98
- store.register('test-zone', {
99
- plugin: 'test-plugin/MockComponentB',
100
- props: {},
101
- });
102
-
103
- wrapper.setProps({ zone: 'test-zone' });
104
-
105
- await flushPromises();
106
- await nextTick();
107
-
108
- expect(federationService.loadAsyncComponent).toHaveBeenCalledTimes(2);
109
- expect(federationService.loadAsyncComponent).toHaveBeenCalledWith(
110
- 'test-plugin/MockComponentA'
111
- );
112
- expect(federationService.loadAsyncComponent).toHaveBeenCalledWith(
113
- 'test-plugin/MockComponentB'
114
- );
115
-
116
- expect(wrapper.vm.components).toHaveLength(2);
117
-
118
- expect(wrapper.vm.components[0].plugin).toBe(
119
- 'test-plugin/MockComponentA'
120
- );
121
- expect(wrapper.vm.components[0].props).toEqual({ title: 'Test' });
122
- expect(JSON.stringify(wrapper.vm.components[0].component)).toEqual(
123
- JSON.stringify(MockComponentA)
124
- );
125
-
126
- expect(wrapper.vm.components[1].plugin).toBe(
127
- 'test-plugin/MockComponentB'
128
- );
129
- expect(wrapper.vm.components[1].props).toEqual({});
130
- expect(JSON.stringify(wrapper.vm.components[1].component)).toEqual(
131
- JSON.stringify(MockComponentB)
132
- );
133
- });
134
- });
135
- });
@@ -1,138 +0,0 @@
1
- import { BasicRemoteModule } from 'src/lifecycle/skeleton';
2
- import { beforeEach, describe, expect, it } from 'vitest';
3
-
4
- describe('Test class: BasicRemoteModule', () => {
5
- describe('Test constructor', () => {
6
- it('should create a module with required parameters', () => {
7
- const module = new BasicRemoteModule(
8
- 'test-module',
9
- 'Test Module',
10
- '1.0.0'
11
- );
12
-
13
- expect(module.id).toBe('test-module');
14
- expect(module.name).toBe('Test Module');
15
- expect(module.version).toBe('1.0.0');
16
- expect(module.description).toBeUndefined();
17
- });
18
-
19
- it('should create a module with description', () => {
20
- const module = new BasicRemoteModule(
21
- 'test-module',
22
- 'Test Module',
23
- '1.0.0',
24
- 'A test module for unit testing'
25
- );
26
-
27
- expect(module.id).toBe('test-module');
28
- expect(module.name).toBe('Test Module');
29
- expect(module.version).toBe('1.0.0');
30
- expect(module.description).toBe('A test module for unit testing');
31
- });
32
- });
33
-
34
- describe('Test lifecycle hooks', () => {
35
- let module;
36
-
37
- beforeEach(() => {
38
- module = new BasicRemoteModule(
39
- 'test-module',
40
- 'Test Module',
41
- '1.0.0',
42
- 'Test description'
43
- );
44
- });
45
-
46
- describe('Test hook: setup', () => {
47
- it('should return success by default', async () => {
48
- const result = await module.setup();
49
-
50
- expect(result).toEqual({ success: true });
51
- });
52
- });
53
-
54
- describe('Test hook: configure', () => {
55
- it('should return success with empty config', async () => {
56
- const result = await module.configure({});
57
-
58
- expect(result).toEqual({ success: true });
59
- });
60
-
61
- it('should return success with valid config', async () => {
62
- const config = {
63
- id: 'test-module',
64
- remoteName: 'testModule',
65
- };
66
-
67
- const result = await module.configure(config);
68
-
69
- expect(result).toEqual({ success: true });
70
- });
71
- });
72
-
73
- describe('Test hook: initialize', () => {
74
- it('should return success by default', async () => {
75
- const result = await module.initialize();
76
-
77
- expect(result).toEqual({ success: true });
78
- });
79
- });
80
-
81
- describe('Test hook: ready', () => {
82
- it('should return success by default', async () => {
83
- const result = await module.ready();
84
-
85
- expect(result).toEqual({ success: true });
86
- });
87
- });
88
-
89
- describe('Test hook: postInit', () => {
90
- it('should return success by default', async () => {
91
- const result = await module.postInit();
92
-
93
- expect(result).toEqual({ success: true });
94
- });
95
- });
96
- });
97
-
98
- describe('Test module extension', () => {
99
- it('should allow extending the class', () => {
100
- class CustomModule extends BasicRemoteModule {
101
- constructor() {
102
- super('custom-module', 'Custom Module', '1.0.0');
103
- }
104
- }
105
-
106
- const module = new CustomModule();
107
-
108
- expect(module).toBeInstanceOf(BasicRemoteModule);
109
- expect(module.id).toBe('custom-module');
110
- expect(module.name).toBe('Custom Module');
111
- expect(module.version).toBe('1.0.0');
112
- });
113
-
114
- it('should allow overriding lifecycle methods', async () => {
115
- class OverriddenModule extends BasicRemoteModule {
116
- constructor() {
117
- super('override-module', 'Override Module', '1.0.0');
118
- }
119
-
120
- async configure(config) {
121
- if (!config.required) {
122
- return { success: false, error: 'Missing required config' };
123
- }
124
- return { success: true };
125
- }
126
- }
127
-
128
- const module = new OverriddenModule();
129
-
130
- const failResult = await module.configure({});
131
- expect(failResult.success).toBe(false);
132
- expect(failResult.error).toBe('Missing required config');
133
-
134
- const successResult = await module.configure({ required: true });
135
- expect(successResult.success).toBe(true);
136
- });
137
- });
138
- });
@@ -1,146 +0,0 @@
1
- import { loadRemote } from '@module-federation/enhanced/runtime';
2
- import { loadAsyncComponent } from 'src/services/federationService';
3
- import { beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { defineAsyncComponent } from 'vue';
5
-
6
- vi.mock('@module-federation/enhanced/runtime');
7
- vi.mock('vue', async () => {
8
- const actual = await vi.importActual('vue');
9
- return {
10
- ...actual,
11
- defineAsyncComponent: vi.fn((loader) => loader),
12
- };
13
- });
14
-
15
- describe('Test service: federationService', () => {
16
- beforeEach(() => {
17
- vi.clearAllMocks();
18
- });
19
-
20
- describe('Test function: loadAsyncComponent', () => {
21
- it('should load a remote component successfully', async () => {
22
- const testComponent = { default: 'RemoteTestComponent' };
23
- const remoteModule = { './TestComponent': testComponent };
24
- vi.mocked(loadRemote).mockResolvedValue(remoteModule['./TestComponent']);
25
-
26
- const loader = loadAsyncComponent('test-plugin/TestComponent');
27
- const result = await loader();
28
-
29
- expect(loadRemote).toHaveBeenCalledTimes(1);
30
- expect(loadRemote).toHaveBeenCalledWith('test-plugin/TestComponent');
31
- expect(defineAsyncComponent).toHaveBeenCalled();
32
- expect(result).toEqual('RemoteTestComponent');
33
- });
34
-
35
- it('should throw an error if remote component export a null default', async () => {
36
- const testComponent = { default: null };
37
- const remoteModule = { './TestComponent': testComponent };
38
- vi.mocked(loadRemote).mockResolvedValue(remoteModule['./TestComponent']);
39
-
40
- const loader = loadAsyncComponent('invalid-plugin/TestComponent');
41
-
42
- await expect(loader()).rejects.toThrow(
43
- 'Failed to load component from invalid-plugin'
44
- );
45
- expect(loadRemote).toHaveBeenCalledWith('invalid-plugin/TestComponent');
46
- });
47
-
48
- it('should throw an error if remote component export an undefined default', async () => {
49
- const testComponent = { default: undefined };
50
- const remoteModule = { './TestComponent': testComponent };
51
- vi.mocked(loadRemote).mockResolvedValue(remoteModule['./TestComponent']);
52
-
53
- const loader = loadAsyncComponent('invalid-plugin/TestComponent');
54
-
55
- await expect(loader()).rejects.toThrow(
56
- 'Failed to load component from invalid-plugin'
57
- );
58
- expect(loadRemote).toHaveBeenCalledWith('invalid-plugin/TestComponent');
59
- });
60
-
61
- it('should throw an error if remote component does not have a default export', async () => {
62
- const testComponent = { toto: vi.fn() };
63
- const remoteModule = { './TestComponent': testComponent };
64
- vi.mocked(loadRemote).mockResolvedValue(remoteModule['./TestComponent']);
65
-
66
- const loader = loadAsyncComponent('invalid-plugin/TestComponent');
67
-
68
- await expect(loader()).rejects.toThrow(
69
- 'Failed to load component from invalid-plugin'
70
- );
71
- expect(loadRemote).toHaveBeenCalledWith('invalid-plugin/TestComponent');
72
- });
73
-
74
- it('should throw an error if module is null', async () => {
75
- vi.mocked(loadRemote).mockResolvedValue(null);
76
-
77
- const loader = loadAsyncComponent('null-plugin/TestComponent');
78
-
79
- await expect(loader()).rejects.toThrow(
80
- 'Failed to load component from null-plugin'
81
- );
82
- });
83
-
84
- it('should throw an error if module is undefined', async () => {
85
- vi.mocked(loadRemote).mockResolvedValue(undefined);
86
-
87
- const loader = loadAsyncComponent('undefined-plugin/TestComponent');
88
-
89
- await expect(loader()).rejects.toThrow(
90
- 'Failed to load component from undefined-plugin'
91
- );
92
- });
93
-
94
- it('should throw an error if loadRemote rejects', async () => {
95
- const error = new Error('Network error');
96
-
97
- vi.mocked(loadRemote).mockRejectedValue(error);
98
-
99
- const loader = loadAsyncComponent('failing-plugin/TestComponent');
100
-
101
- await expect(loader()).rejects.toThrow('Network error');
102
- expect(loadRemote).toHaveBeenCalledWith('failing-plugin/TestComponent');
103
- });
104
-
105
- it('should handle different plugin names', async () => {
106
- const plugins = ['plugin-a', 'plugin-b', 'plugin-c'];
107
- const testComponentA = { default: 'RemoteTestComponentA' };
108
- const remoteModuleA = { './TestComponent': testComponentA };
109
- const testComponentB = { default: 'RemoteTestComponentB' };
110
- const remoteModuleB = { './TestComponent': testComponentB };
111
- const testComponentC = { default: 'RemoteTestComponentC' };
112
- const remoteModuleC = { './TestComponent': testComponentC };
113
-
114
- vi.mocked(loadRemote)
115
- .mockResolvedValueOnce(remoteModuleA['./TestComponent'])
116
- .mockResolvedValueOnce(remoteModuleB['./TestComponent'])
117
- .mockResolvedValueOnce(remoteModuleC['./TestComponent']);
118
-
119
- for (const plugin of plugins) {
120
- const loader = loadAsyncComponent(`${plugin}/TestComponent`);
121
- const result = await loader();
122
- const lastChar = plugin.charAt(plugin.length - 1).toUpperCase();
123
-
124
- expect(loadRemote).toHaveBeenCalledWith(`${plugin}/TestComponent`);
125
- expect(result).toEqual(`RemoteTestComponent${lastChar}`);
126
- }
127
-
128
- expect(loadRemote).toHaveBeenCalledTimes(3);
129
- });
130
-
131
- it('should handle module with additional exports', async () => {
132
- const testComponent = {
133
- default: 'RemoteTestComponent',
134
- namedExport1: 'value1',
135
- namedExport2: 'value2',
136
- };
137
- const remoteModule = { './TestComponent': testComponent };
138
- vi.mocked(loadRemote).mockResolvedValue(remoteModule['./TestComponent']);
139
-
140
- const loader = loadAsyncComponent('multi-export-plugin');
141
- const result = await loader();
142
-
143
- expect(result).toEqual('RemoteTestComponent');
144
- });
145
- });
146
- });