@linagora/linid-im-front-corelib 0.0.4 → 0.0.5
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.
- package/.github/workflows/pull-request.yml +25 -0
- package/CHANGELOG.md +2 -0
- package/CONTRIBUTING.md +7 -2
- package/dist/package.json +10 -3
- package/eslint.config.js +2 -1
- package/package.json +10 -3
- package/tests/unit/components/LinidZoneRenderer.spec.js +135 -0
- package/tests/unit/lifecycle/skeleton.spec.js +138 -0
- package/tests/unit/services/federationService.spec.js +146 -0
- package/tests/unit/stores/linidZoneStore.spec.js +94 -0
- package/tsconfig.json +11 -27
- package/tsconfig.lib.json +20 -0
- package/tsconfig.node.json +9 -0
- package/tsconfig.spec.json +16 -0
- package/vite.config.ts +11 -16
- package/vitest.config.ts +19 -0
- package/dist/types/vite.config.d.ts +0 -2
|
@@ -120,3 +120,28 @@ jobs:
|
|
|
120
120
|
- uses: gensecaihq/Shai-Hulud-2.0-Detector@v1
|
|
121
121
|
with:
|
|
122
122
|
fail-on-critical: true
|
|
123
|
+
|
|
124
|
+
unit-tests:
|
|
125
|
+
name: Unit tests
|
|
126
|
+
runs-on: ubuntu-latest
|
|
127
|
+
needs: [build]
|
|
128
|
+
steps:
|
|
129
|
+
- uses: actions/checkout@v4
|
|
130
|
+
|
|
131
|
+
- name: Setup Node.js and pnpm
|
|
132
|
+
uses: ./.github/actions/setup-node-pnpm
|
|
133
|
+
with:
|
|
134
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
135
|
+
pnpm-version: ${{ env.PNPM_VERSION }}
|
|
136
|
+
|
|
137
|
+
- name: Run unit tests
|
|
138
|
+
run: |
|
|
139
|
+
echo '```bash' > coverage.txt
|
|
140
|
+
pnpm test:ci | sed 's/\x1b\[[0-9;]*m//g' >> coverage.txt
|
|
141
|
+
echo '```' >> coverage.txt
|
|
142
|
+
|
|
143
|
+
- name: Comment PR
|
|
144
|
+
uses: JoseThen/comment-pr@v1.2.0
|
|
145
|
+
with:
|
|
146
|
+
file_path: ./coverage.txt
|
|
147
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.0.5](https://github.com/linagora/linid-im-front-corelib/compare/v0.0.4...v0.0.5) (2025-12-03)
|
|
6
|
+
|
|
5
7
|
### [0.0.4](https://github.com/linagora/linid-im-front-corelib/compare/v0.0.3...v0.0.4) (2025-12-01)
|
|
6
8
|
|
|
7
9
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -161,9 +161,14 @@ npm run dev
|
|
|
161
161
|
## **🧪 Run Tests**
|
|
162
162
|
|
|
163
163
|
```sh
|
|
164
|
-
pnpm test
|
|
164
|
+
pnpm test # Runs the full test suite once
|
|
165
|
+
pnpm test:watch # Runs tests in watch mode
|
|
166
|
+
pnpm test:coverage # Generates a coverage report
|
|
167
|
+
|
|
165
168
|
# or (not recommended by the dev team)
|
|
166
|
-
npm run test
|
|
169
|
+
npm run test # Runs the full test suite once
|
|
170
|
+
npm run test:watch # Runs tests in watch mode
|
|
171
|
+
npm run test:coverage # Generates a coverage report
|
|
167
172
|
```
|
|
168
173
|
|
|
169
174
|
---
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linagora/linid-im-front-corelib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Core library of the LinID Identity Manager project. Provides shared types, services, components, and utilities for front-end and plugin, enabling consistent integration across the LinID ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core-lib.umd.js",
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly",
|
|
17
|
-
"test": "vitest",
|
|
16
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.lib.json",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:coverage": "vitest --run --coverage",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:ci": "vitest run --coverage --reporter=dot",
|
|
18
21
|
"dev": "vite",
|
|
19
22
|
"lint": "eslint . --max-warnings 0",
|
|
20
23
|
"lint:fix": "eslint . --fix",
|
|
@@ -58,15 +61,19 @@
|
|
|
58
61
|
"@eslint/js": "9.39.1",
|
|
59
62
|
"@types/node": "20.19.9",
|
|
60
63
|
"@vitejs/plugin-vue": "6.0.1",
|
|
64
|
+
"@vitest/coverage-v8": "4.0.8",
|
|
61
65
|
"@vue/eslint-config-prettier": "10.2.0",
|
|
62
66
|
"@vue/eslint-config-typescript": "14.6.0",
|
|
67
|
+
"@vue/test-utils": "2.4.6",
|
|
63
68
|
"eslint": "9.39.1",
|
|
64
69
|
"eslint-plugin-headers": "1.3.3",
|
|
65
70
|
"eslint-plugin-jsdoc": "61.2.1",
|
|
66
71
|
"eslint-plugin-vue": "10.5.1",
|
|
72
|
+
"happy-dom": "20.0.10",
|
|
67
73
|
"prettier": "3.6.2",
|
|
68
74
|
"typescript": "5.9.3",
|
|
69
75
|
"vite": "7.2.2",
|
|
76
|
+
"vite-tsconfig-paths": "5.1.4",
|
|
70
77
|
"vitest": "4.0.8",
|
|
71
78
|
"vue-tsc": "3.1.3"
|
|
72
79
|
},
|
package/eslint.config.js
CHANGED
|
@@ -123,9 +123,10 @@ export default defineConfigWithVueTs(
|
|
|
123
123
|
files: [
|
|
124
124
|
'**/*.test.ts',
|
|
125
125
|
'**/*.spec.ts',
|
|
126
|
+
'**/*.test.js',
|
|
127
|
+
'**/*.spec.js',
|
|
126
128
|
'**/__tests__/**',
|
|
127
129
|
'**/*.config.*',
|
|
128
|
-
'**/*.config.*',
|
|
129
130
|
],
|
|
130
131
|
rules: {
|
|
131
132
|
'jsdoc/require-jsdoc': 'off',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linagora/linid-im-front-corelib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Core library of the LinID Identity Manager project. Provides shared types, services, components, and utilities for front-end and plugin, enabling consistent integration across the LinID ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/core-lib.umd.js",
|
|
@@ -47,21 +47,28 @@
|
|
|
47
47
|
"@eslint/js": "9.39.1",
|
|
48
48
|
"@types/node": "20.19.9",
|
|
49
49
|
"@vitejs/plugin-vue": "6.0.1",
|
|
50
|
+
"@vitest/coverage-v8": "4.0.8",
|
|
50
51
|
"@vue/eslint-config-prettier": "10.2.0",
|
|
51
52
|
"@vue/eslint-config-typescript": "14.6.0",
|
|
53
|
+
"@vue/test-utils": "2.4.6",
|
|
52
54
|
"eslint": "9.39.1",
|
|
53
55
|
"eslint-plugin-headers": "1.3.3",
|
|
54
56
|
"eslint-plugin-jsdoc": "61.2.1",
|
|
55
57
|
"eslint-plugin-vue": "10.5.1",
|
|
58
|
+
"happy-dom": "20.0.10",
|
|
56
59
|
"prettier": "3.6.2",
|
|
57
60
|
"typescript": "5.9.3",
|
|
58
61
|
"vite": "7.2.2",
|
|
62
|
+
"vite-tsconfig-paths": "5.1.4",
|
|
59
63
|
"vitest": "4.0.8",
|
|
60
64
|
"vue-tsc": "3.1.3"
|
|
61
65
|
},
|
|
62
66
|
"scripts": {
|
|
63
|
-
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly",
|
|
64
|
-
"test": "vitest",
|
|
67
|
+
"build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.lib.json",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:coverage": "vitest --run --coverage",
|
|
70
|
+
"test:watch": "vitest",
|
|
71
|
+
"test:ci": "vitest run --coverage --reporter=dot",
|
|
65
72
|
"dev": "vite",
|
|
66
73
|
"lint": "eslint . --max-warnings 0",
|
|
67
74
|
"lint:fix": "eslint . --fix",
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createPinia, setActivePinia } from 'pinia';
|
|
2
|
+
import { useLinidZoneStore } from 'src/stores/linidZoneStore';
|
|
3
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
describe('Test store: linidZoneStore', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
setActivePinia(createPinia());
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('Test initial state', () => {
|
|
11
|
+
it('should initialize with empty zones', () => {
|
|
12
|
+
const store = useLinidZoneStore();
|
|
13
|
+
|
|
14
|
+
expect(store.zones).toEqual({});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Test function: register', () => {
|
|
19
|
+
it('should register an entry in a new zone', () => {
|
|
20
|
+
const store = useLinidZoneStore();
|
|
21
|
+
const entry = {
|
|
22
|
+
plugin: 'test-plugin/TestComponent',
|
|
23
|
+
props: {},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
store.register('list-page.sidebar', entry);
|
|
27
|
+
|
|
28
|
+
expect(store.zones['list-page.sidebar']).toBeDefined();
|
|
29
|
+
expect(store.zones['list-page.sidebar']).toHaveLength(1);
|
|
30
|
+
expect(store.zones['list-page.sidebar'][0]).toEqual(entry);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should register multiple entries in the same zone', () => {
|
|
34
|
+
const store = useLinidZoneStore();
|
|
35
|
+
const entry1 = {
|
|
36
|
+
plugin: 'plugin-1/Component1',
|
|
37
|
+
props: {},
|
|
38
|
+
};
|
|
39
|
+
const entry2 = {
|
|
40
|
+
plugin: 'plugin-2/Component2',
|
|
41
|
+
props: { value: 42 },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
store.register('list-page.sidebar', entry1);
|
|
45
|
+
store.register('list-page.sidebar', entry2);
|
|
46
|
+
|
|
47
|
+
expect(store.zones['list-page.sidebar']).toHaveLength(2);
|
|
48
|
+
expect(store.zones['list-page.sidebar'][0]).toEqual(entry1);
|
|
49
|
+
expect(store.zones['list-page.sidebar'][1]).toEqual(entry2);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should register entries in different zones independently', () => {
|
|
53
|
+
const store = useLinidZoneStore();
|
|
54
|
+
const headerEntry = {
|
|
55
|
+
plugin: 'header-plugin/HeaderComponent',
|
|
56
|
+
props: {},
|
|
57
|
+
};
|
|
58
|
+
const footerEntry = {
|
|
59
|
+
plugin: 'footer-plugin/FooterComponent',
|
|
60
|
+
props: {},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
store.register('list-page.header', headerEntry);
|
|
64
|
+
store.register('list-page.footer', footerEntry);
|
|
65
|
+
|
|
66
|
+
expect(store.zones['list-page.header']).toHaveLength(1);
|
|
67
|
+
expect(store.zones['list-page.footer']).toHaveLength(1);
|
|
68
|
+
expect(store.zones['list-page.header'][0]).toEqual(headerEntry);
|
|
69
|
+
expect(store.zones['list-page.footer'][0]).toEqual(footerEntry);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle entries with complex props', () => {
|
|
73
|
+
const store = useLinidZoneStore();
|
|
74
|
+
const entry = {
|
|
75
|
+
plugin: 'complex-plugin/ComplexComponent',
|
|
76
|
+
props: {
|
|
77
|
+
title: 'Test Title',
|
|
78
|
+
count: 123,
|
|
79
|
+
enabled: true,
|
|
80
|
+
config: {
|
|
81
|
+
nested: {
|
|
82
|
+
value: 'deep',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
items: ['a', 'b', 'c'],
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
store.register('list-page.body', entry);
|
|
90
|
+
|
|
91
|
+
expect(store.zones['list-page.body'][0].props).toEqual(entry.props);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"skipLibCheck": true,
|
|
14
|
-
"forceConsistentCasingInFileNames": true,
|
|
15
|
-
"resolveJsonModule": true,
|
|
16
|
-
"types": [
|
|
17
|
-
"node",
|
|
18
|
-
"vite/client",
|
|
19
|
-
"vue"
|
|
20
|
-
],
|
|
21
|
-
"declaration": true,
|
|
22
|
-
"declarationDir": "dist/types",
|
|
23
|
-
"outDir": "dist"
|
|
24
|
-
},
|
|
25
|
-
"include": [
|
|
26
|
-
"src/**/*.ts",
|
|
27
|
-
"src/**/*.vue",
|
|
28
|
-
"vite.config.ts"
|
|
2
|
+
"files": [],
|
|
3
|
+
"references": [
|
|
4
|
+
{
|
|
5
|
+
"path": "./tsconfig.node.json"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"path": "./tsconfig.lib.json"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "./tsconfig.spec.json"
|
|
12
|
+
}
|
|
29
13
|
]
|
|
30
14
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"lib": ["ESNext", "DOM"],
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"types": ["node", "vite/client", "vue"],
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationDir": "dist/types",
|
|
16
|
+
"outDir": "dist"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"],
|
|
19
|
+
"exclude": ["vite.config.ts", "tests", "vitest.config.ts"]
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"outDir": "./out-tsc/vitest",
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"module": "esnext",
|
|
8
|
+
"moduleResolution": "bundler"
|
|
9
|
+
},
|
|
10
|
+
"include": ["tests", "src"],
|
|
11
|
+
"references": [
|
|
12
|
+
{
|
|
13
|
+
"path": "./tsconfig.lib.json"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import {fileURLToPath} from "url";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
1
|
+
import vue from '@vitejs/plugin-vue';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { defineConfig } from 'vite';
|
|
10
5
|
|
|
11
6
|
export default defineConfig({
|
|
12
7
|
plugins: [
|
|
13
8
|
vue(),
|
|
14
9
|
|
|
15
10
|
{
|
|
16
|
-
name:
|
|
11
|
+
name: 'copy-package-json',
|
|
17
12
|
closeBundle() {
|
|
18
|
-
const src = path.resolve(__dirname,
|
|
19
|
-
const dest = path.resolve(__dirname,
|
|
13
|
+
const src = path.resolve(__dirname, 'package.json');
|
|
14
|
+
const dest = path.resolve(__dirname, 'dist', 'package.json');
|
|
20
15
|
if (fs.existsSync(src)) {
|
|
21
16
|
fs.copyFileSync(src, dest);
|
|
22
17
|
}
|
|
@@ -26,15 +21,15 @@ export default defineConfig({
|
|
|
26
21
|
|
|
27
22
|
build: {
|
|
28
23
|
lib: {
|
|
29
|
-
entry: path.resolve(__dirname,
|
|
30
|
-
name:
|
|
24
|
+
entry: path.resolve(__dirname, 'src/index.ts'),
|
|
25
|
+
name: 'CoreLib',
|
|
31
26
|
fileName: (format) => `core-lib.${format}.js`,
|
|
32
27
|
},
|
|
33
28
|
rollupOptions: {
|
|
34
|
-
external: [
|
|
29
|
+
external: ['vue'],
|
|
35
30
|
output: {
|
|
36
31
|
globals: {
|
|
37
|
-
vue:
|
|
32
|
+
vue: 'Vue',
|
|
38
33
|
},
|
|
39
34
|
},
|
|
40
35
|
},
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import vue from '@vitejs/plugin-vue';
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
test: {
|
|
7
|
+
environment: 'happy-dom',
|
|
8
|
+
globals: true,
|
|
9
|
+
include: ['tests/unit/**/*.{test,spec}.js'],
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: 'v8',
|
|
12
|
+
reporter: ['text', 'html', 'lcov'],
|
|
13
|
+
reportsDirectory: './coverage',
|
|
14
|
+
include: ['src/**/*.{ts,js,vue}'],
|
|
15
|
+
exclude: ['**/tests/**', 'src/types/**', 'src/index.ts'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
plugins: [vue(), tsconfigPaths()],
|
|
19
|
+
});
|