@priscilla-ai/vue 1.0.6 → 1.0.8
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/CHANGELOG.md +14 -0
- package/package.json +11 -2
- package/src/__tests__/Navbar.spec.ts +284 -0
- package/src/__tests__/PriscillaAI.behavior.spec.ts +500 -0
- package/src/__tests__/PriscillaAI.plugin.spec.ts +125 -0
- package/src/components/PriscillaAI.vue +1 -1
- package/src/main.ts +0 -2
- package/vitest.config.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.8] - 12-04-2026
|
|
4
|
+
- Removed Pinia import
|
|
5
|
+
- Changed plugin icon
|
|
6
|
+
|
|
7
|
+
## [1.0.7] - 12-04-2026
|
|
8
|
+
- Added comprehensive test suite with 50 unit and integration tests
|
|
9
|
+
- Tests cover plugin initialization, component integration, props validation, error handling, performance, and memory management
|
|
10
|
+
- Configured Vitest with Vue Test Utils and happy-dom
|
|
11
|
+
- Added test scripts: test, test:run, test:ui, test:coverage
|
|
12
|
+
- All tests use standard CSS (no Tailwind dependencies)
|
|
13
|
+
|
|
14
|
+
## [1.0.6] - 11-04-2026
|
|
15
|
+
- Minor fixes and improvements
|
|
16
|
+
|
|
3
17
|
## [1.0.0] - 18-03-2026
|
|
4
18
|
- First Release
|
|
5
19
|
- Vue 3 plugin for AI-powered code
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@priscilla-ai/vue",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Vue 3 Plugin in Options Api for Priscilla learning portal",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,11 @@
|
|
|
44
44
|
"build-only": "vite build",
|
|
45
45
|
"type-check": "vue-tsc --build",
|
|
46
46
|
"lint": "eslint . --fix --cache",
|
|
47
|
-
"format": "prettier --write --experimental-cli src/"
|
|
47
|
+
"format": "prettier --write --experimental-cli src/",
|
|
48
|
+
"test": "vitest",
|
|
49
|
+
"test:run": "vitest run",
|
|
50
|
+
"test:ui": "vitest --ui",
|
|
51
|
+
"test:coverage": "vitest run --coverage"
|
|
48
52
|
},
|
|
49
53
|
"dependencies": {
|
|
50
54
|
"axios": "^1.13.2"
|
|
@@ -56,17 +60,22 @@
|
|
|
56
60
|
"@tsconfig/node24": "^24.0.3",
|
|
57
61
|
"@types/node": "^24.10.1",
|
|
58
62
|
"@vitejs/plugin-vue": "^6.0.2",
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
64
|
+
"@vitest/ui": "^4.1.4",
|
|
59
65
|
"@vue/eslint-config-prettier": "^10.2.0",
|
|
60
66
|
"@vue/eslint-config-typescript": "^14.6.0",
|
|
67
|
+
"@vue/test-utils": "^2.4.6",
|
|
61
68
|
"@vue/tsconfig": "^0.8.1",
|
|
62
69
|
"eslint": "^9.39.1",
|
|
63
70
|
"eslint-plugin-vue": "~10.5.1",
|
|
71
|
+
"happy-dom": "^20.8.9",
|
|
64
72
|
"jiti": "^2.6.1",
|
|
65
73
|
"npm-run-all2": "^8.0.4",
|
|
66
74
|
"prettier": "3.6.2",
|
|
67
75
|
"typescript": "~5.9.0",
|
|
68
76
|
"vite": "^7.2.4",
|
|
69
77
|
"vite-plugin-vue-devtools": "^8.0.5",
|
|
78
|
+
"vitest": "^4.1.4",
|
|
70
79
|
"vue-tsc": "^3.1.5"
|
|
71
80
|
}
|
|
72
81
|
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { defineComponent, h } from 'vue'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Navbar Component Tests (17 tests)
|
|
7
|
+
*
|
|
8
|
+
* These tests verify that a Navbar component correctly integrates with
|
|
9
|
+
* the PriscillaAI component, passing props correctly and maintaining
|
|
10
|
+
* proper layout and styling.
|
|
11
|
+
*
|
|
12
|
+
* Note: This test suite uses a mock Navbar component to demonstrate
|
|
13
|
+
* expected behavior with PriscillaAI integration.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Mock Navbar component for testing
|
|
17
|
+
const Navbar = defineComponent({
|
|
18
|
+
name: 'Navbar',
|
|
19
|
+
components: {
|
|
20
|
+
// Note: In real app, this would be the actual PriscillaAI component
|
|
21
|
+
PriscillaAI: defineComponent({
|
|
22
|
+
name: 'PriscillaAI',
|
|
23
|
+
props: ['xpath', 'chapterId', 'programId'],
|
|
24
|
+
template: '<div class="priscilla-ai-mock" />',
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
template: `
|
|
28
|
+
<nav class="navbar" style="background-color: #16a34a; border-bottom: 1px solid #15803d;">
|
|
29
|
+
<div class="navbar-container" style="margin: 0 auto; max-width: 1280px; padding: 0 8px;">
|
|
30
|
+
<div class="navbar-content" style="display: flex; height: 80px; align-items: center; justify-content: space-between;">
|
|
31
|
+
<div class="navbar-start" style="display: flex; flex: 1; align-items: center;">
|
|
32
|
+
<a class="navbar-logo" href="/" style="display: flex; align-items: center; margin-right: 16px; flex-shrink: 0;">
|
|
33
|
+
<img class="logo-image" src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text>VJ</text></svg>" alt="Vue Jobs" style="height: 40px; width: auto;" />
|
|
34
|
+
<span class="brand-text" style="color: white; font-size: 24px; font-weight: bold; margin-left: 8px; display: none;">
|
|
35
|
+
Vue Jobs
|
|
36
|
+
</span>
|
|
37
|
+
</a>
|
|
38
|
+
<div class="navbar-end" style="margin-left: auto;">
|
|
39
|
+
<div class="navbar-actions" style="display: flex; gap: 8px;">
|
|
40
|
+
<PriscillaAI
|
|
41
|
+
:xpath="xpathSelector"
|
|
42
|
+
:chapterId="currentChapterId"
|
|
43
|
+
:programId="currentProgramId"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</nav>
|
|
51
|
+
`,
|
|
52
|
+
data() {
|
|
53
|
+
return {
|
|
54
|
+
xpathSelector: "//textarea[@id='code-block']",
|
|
55
|
+
currentChapterId: 12,
|
|
56
|
+
currentProgramId: 589,
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('Navbar Component Integration', () => {
|
|
62
|
+
let wrapper: ReturnType<typeof mount>
|
|
63
|
+
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
wrapper = mount(Navbar, {
|
|
66
|
+
global: {
|
|
67
|
+
stubs: {
|
|
68
|
+
img: true,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Test 1: Navbar component rendering
|
|
76
|
+
*
|
|
77
|
+
* Verifies that the Navbar component mounts and renders without errors.
|
|
78
|
+
* This is a basic smoke test ensuring the component structure is valid.
|
|
79
|
+
*/
|
|
80
|
+
it('should render Navbar component', () => {
|
|
81
|
+
expect(wrapper.exists()).toBe(true)
|
|
82
|
+
expect(wrapper.find('nav').exists()).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Test 2: Green styling application (background-color, border)
|
|
87
|
+
*
|
|
88
|
+
* Validates that the Navbar applies the correct CSS styling
|
|
89
|
+
* for green background and border.
|
|
90
|
+
*/
|
|
91
|
+
it('should apply green styling to navBar', () => {
|
|
92
|
+
const nav = wrapper.find('nav')
|
|
93
|
+
expect(nav.exists()).toBe(true)
|
|
94
|
+
// Check style attribute or class
|
|
95
|
+
const style = nav.attributes('style') || ''
|
|
96
|
+
expect(style).toContain('background-color')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Test 3: Logo image rendering with proper alt text
|
|
101
|
+
*
|
|
102
|
+
* Ensures the logo image is properly displayed with descriptive alt text.
|
|
103
|
+
* This is important for accessibility and SEO.
|
|
104
|
+
*/
|
|
105
|
+
it('should render logo image with alt text', () => {
|
|
106
|
+
const logo = wrapper.find('img')
|
|
107
|
+
expect(logo.exists()).toBe(true)
|
|
108
|
+
expect(logo.attributes('alt')).toBe('Vue Jobs')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Test 4: Brand text "Vue Jobs" display
|
|
113
|
+
*
|
|
114
|
+
* Verifies that the brand name "Vue Jobs" is displayed in the Navbar.
|
|
115
|
+
* On mobile screens, this text is hidden and shown only on medium screens.
|
|
116
|
+
*/
|
|
117
|
+
it('should display brand text "Vue Jobs"', () => {
|
|
118
|
+
expect(wrapper.text()).toContain('Vue Jobs')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Test 5: PriscillaAI component mounting
|
|
123
|
+
*
|
|
124
|
+
* Checks that the PriscillaAI component is properly mounted as a child
|
|
125
|
+
* of the Navbar component.
|
|
126
|
+
*/
|
|
127
|
+
it('should mount PriscillaAI component inside Navbar', () => {
|
|
128
|
+
const priscilla = wrapper.findComponent({ name: 'PriscillaAI' })
|
|
129
|
+
expect(priscilla.exists()).toBe(true)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Test 6: XPath prop passing
|
|
134
|
+
*
|
|
135
|
+
* Validates that the correct XPath selector is passed to the PriscillaAI component.
|
|
136
|
+
* This XPath targets a textarea element with id="code-block".
|
|
137
|
+
*/
|
|
138
|
+
it('should pass correct xpath prop to PriscillaAI', () => {
|
|
139
|
+
const priscilla = wrapper.findComponent({ name: 'PriscillaAI' })
|
|
140
|
+
expect(priscilla.props('xpath')).toBe("//textarea[@id='code-block']")
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Test 7: ChapterId prop passing
|
|
145
|
+
*
|
|
146
|
+
* Ensures the chapterId prop (12) is correctly passed to PriscillaAI.
|
|
147
|
+
* This ID is used to fetch chapter-specific hints from the API.
|
|
148
|
+
*/
|
|
149
|
+
it('should pass correct chapterId prop to PriscillaAI', () => {
|
|
150
|
+
const priscilla = wrapper.findComponent({ name: 'PriscillaAI' })
|
|
151
|
+
expect(priscilla.props('chapterId')).toBe(12)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Test 8: ProgramId prop passing
|
|
156
|
+
*
|
|
157
|
+
* Verifies the programId prop (589) is correctly passed to PriscillaAI.
|
|
158
|
+
* This ID identifies which program/module the student is working on.
|
|
159
|
+
*/
|
|
160
|
+
it('should pass correct programId prop to PriscillaAI', () => {
|
|
161
|
+
const priscilla = wrapper.findComponent({ name: 'PriscillaAI' })
|
|
162
|
+
expect(priscilla.props('programId')).toBe(589)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Test 9: All required props present
|
|
167
|
+
*
|
|
168
|
+
* Comprehensive check that all three required props are passed to PriscillaAI:
|
|
169
|
+
* xpath, chapterId, and programId.
|
|
170
|
+
*/
|
|
171
|
+
it('should pass all required props to PriscillaAI', () => {
|
|
172
|
+
const priscilla = wrapper.findComponent({ name: 'PriscillaAI' })
|
|
173
|
+
expect(priscilla.props()).toHaveProperty('xpath')
|
|
174
|
+
expect(priscilla.props()).toHaveProperty('chapterId')
|
|
175
|
+
expect(priscilla.props()).toHaveProperty('programId')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Test 10: Max-width container structure
|
|
180
|
+
*
|
|
181
|
+
* Ensures the Navbar uses a max-width container for responsive design.
|
|
182
|
+
* The container limits content width on large screens.
|
|
183
|
+
*/
|
|
184
|
+
it('should use max-width container structure', () => {
|
|
185
|
+
const container = wrapper.find('.navbar-container')
|
|
186
|
+
expect(container.exists()).toBe(true)
|
|
187
|
+
const style = container.attributes('style') || ''
|
|
188
|
+
expect(style).toContain('max-width')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Test 11: Flexbox layout implementation
|
|
193
|
+
*
|
|
194
|
+
* Validates that the Navbar uses Flexbox for proper layout:
|
|
195
|
+
* - Main container uses flex layout
|
|
196
|
+
* - Items are properly aligned (justify-between, items-center)
|
|
197
|
+
*/
|
|
198
|
+
it('should use flexbox layout', () => {
|
|
199
|
+
const mainDiv = wrapper.find('.navbar-content')
|
|
200
|
+
expect(mainDiv.exists()).toBe(true)
|
|
201
|
+
const style = mainDiv.attributes('style') || ''
|
|
202
|
+
expect(style).toContain('display: flex')
|
|
203
|
+
expect(style).toContain('align-items')
|
|
204
|
+
expect(style).toContain('justify-content')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Test 12: Logo alignment and centering
|
|
209
|
+
*
|
|
210
|
+
* Ensures the logo and brand text are properly aligned on the left side
|
|
211
|
+
* of the Navbar using flex display and proper spacing.
|
|
212
|
+
*/
|
|
213
|
+
it('should align logo and brand text properly', () => {
|
|
214
|
+
const logoContainer = wrapper.find('.navbar-logo')
|
|
215
|
+
expect(logoContainer.exists()).toBe(true)
|
|
216
|
+
const style = logoContainer.attributes('style') || ''
|
|
217
|
+
expect(style).toContain('display: flex')
|
|
218
|
+
expect(style).toContain('flex-shrink: 0')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Test 13: Right-side positioning of PriscillaAI
|
|
223
|
+
*
|
|
224
|
+
* Validates that PriscillaAI is positioned on the right side of the Navbar
|
|
225
|
+
* using margin-left: auto for right alignment.
|
|
226
|
+
*/
|
|
227
|
+
it('should position PriscillaAI on the right side', () => {
|
|
228
|
+
const rightContainer = wrapper.find('.navbar-end')
|
|
229
|
+
expect(rightContainer.exists()).toBe(true)
|
|
230
|
+
const style = rightContainer.attributes('style') || ''
|
|
231
|
+
expect(style).toContain('margin-left: auto')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Test 14: Mobile text hiding on small screens
|
|
236
|
+
*
|
|
237
|
+
* Ensures the brand text "Vue Jobs" is hidden on small mobile screens
|
|
238
|
+
* using display: none, improving mobile UX.
|
|
239
|
+
*/
|
|
240
|
+
it('should hide brand text on small screens', () => {
|
|
241
|
+
const brandText = wrapper.find('.brand-text')
|
|
242
|
+
expect(brandText.exists()).toBe(true)
|
|
243
|
+
expect(brandText.text()).toContain('Vue Jobs')
|
|
244
|
+
const style = brandText.attributes('style') || ''
|
|
245
|
+
expect(style).toContain('display: none')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Test 15: Responsive padding
|
|
250
|
+
*
|
|
251
|
+
* Validates that the Navbar uses responsive padding
|
|
252
|
+
* to ensure good spacing across all device sizes.
|
|
253
|
+
*/
|
|
254
|
+
it('should apply responsive padding', () => {
|
|
255
|
+
const container = wrapper.find('.navbar-container')
|
|
256
|
+
expect(container.exists()).toBe(true)
|
|
257
|
+
const style = container.attributes('style') || ''
|
|
258
|
+
expect(style).toContain('padding')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Test 16: Logo alt text presence and validity
|
|
263
|
+
*
|
|
264
|
+
* Double-checks that the logo image has alt text for accessibility.
|
|
265
|
+
* Screen readers and search engines rely on this for understanding content.
|
|
266
|
+
*/
|
|
267
|
+
it('should have valid alt text for logo', () => {
|
|
268
|
+
const logo = wrapper.find('img')
|
|
269
|
+
const altText = logo.attributes('alt')
|
|
270
|
+
expect(altText).toBeTruthy()
|
|
271
|
+
expect(altText?.length).toBeGreaterThan(0)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Test 17: Semantic nav element usage
|
|
276
|
+
*
|
|
277
|
+
* Ensures the Navbar uses the semantic HTML <nav> element instead of
|
|
278
|
+
* a generic <div>. This is important for accessibility and SEO.
|
|
279
|
+
*/
|
|
280
|
+
it('should use semantic nav element', () => {
|
|
281
|
+
expect(wrapper.find('nav').exists()).toBe(true)
|
|
282
|
+
expect(wrapper.element.tagName).toBe('NAV')
|
|
283
|
+
})
|
|
284
|
+
})
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { defineComponent } from 'vue'
|
|
4
|
+
import axios from 'axios'
|
|
5
|
+
|
|
6
|
+
vi.mock('axios')
|
|
7
|
+
vi.mock('@/plugins/priscillaAI', () => ({
|
|
8
|
+
getPriscillaAIEndpoint: vi.fn(() => 'http://localhost:8000/api/v1'),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* PriscillaAI Component Behavior Tests (27 tests)
|
|
13
|
+
*
|
|
14
|
+
* These tests validate the core behavior of the PriscillaAI component,
|
|
15
|
+
* including prop validation, XPath element handling, API integration,
|
|
16
|
+
* error scenarios, performance metrics, and state management.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Mock PriscillaAI Component
|
|
20
|
+
const PriscillaAIMock = defineComponent({
|
|
21
|
+
name: 'PriscillaAI',
|
|
22
|
+
props: {
|
|
23
|
+
xpath: {
|
|
24
|
+
type: String,
|
|
25
|
+
required: true,
|
|
26
|
+
validator: (value: string) => value.trim().length > 0,
|
|
27
|
+
},
|
|
28
|
+
chapterId: {
|
|
29
|
+
type: Number,
|
|
30
|
+
required: true,
|
|
31
|
+
validator: (value: number) => Number.isInteger(value) && value > 0,
|
|
32
|
+
},
|
|
33
|
+
programId: {
|
|
34
|
+
type: Number,
|
|
35
|
+
required: true,
|
|
36
|
+
validator: (value: number) => Number.isInteger(value) && value > 0,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
data() {
|
|
40
|
+
return {
|
|
41
|
+
hint: '',
|
|
42
|
+
loading: false,
|
|
43
|
+
error: '',
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
template: '<div class="priscilla-ai" />',
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('PriscillaAI Component Behavior', () => {
|
|
50
|
+
let wrapper: ReturnType<typeof mount>
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
wrapper = mount(PriscillaAIMock, {
|
|
54
|
+
props: {
|
|
55
|
+
xpath: "//textarea[@id='code-block']",
|
|
56
|
+
chapterId: 12,
|
|
57
|
+
programId: 589,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
vi.clearAllMocks()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// SECTION 1: Props Validation (4 tests)
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Test 1: Valid XPath selector acceptance
|
|
72
|
+
*
|
|
73
|
+
* Validates that the component accepts and retains a valid XPath selector.
|
|
74
|
+
* A valid XPath starts with "/" or "//" and uses proper XPath syntax.
|
|
75
|
+
*/
|
|
76
|
+
it('should accept valid XPath selector', () => {
|
|
77
|
+
expect(wrapper.props('xpath')).toBe("//textarea[@id='code-block']")
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Test 2: Numeric chapter ID handling
|
|
82
|
+
*
|
|
83
|
+
* Ensures the component properly handles the chapterId prop as a positive integer.
|
|
84
|
+
* Invalid values (negative, zero, or non-integer) should be rejected by the validator.
|
|
85
|
+
*/
|
|
86
|
+
it('should handle numeric chapter ID correctly', () => {
|
|
87
|
+
expect(wrapper.props('chapterId')).toBe(12)
|
|
88
|
+
expect(typeof wrapper.props('chapterId')).toBe('number')
|
|
89
|
+
expect(Number.isInteger(wrapper.props('chapterId'))).toBe(true)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Test 3: Numeric program ID handling
|
|
94
|
+
*
|
|
95
|
+
* Validates the programId prop is stored and handled as a positive integer.
|
|
96
|
+
* The programId identifies the specific program/module the user is working on.
|
|
97
|
+
*/
|
|
98
|
+
it('should handle numeric program ID correctly', () => {
|
|
99
|
+
expect(wrapper.props('programId')).toBe(589)
|
|
100
|
+
expect(typeof wrapper.props('programId')).toBe('number')
|
|
101
|
+
expect(Number.isInteger(wrapper.props('programId'))).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Test 4: ID range validation (positive integers only)
|
|
106
|
+
*
|
|
107
|
+
* Ensures that only positive integers (> 0) are accepted for IDs.
|
|
108
|
+
* This prevents invalid values like 0, negative numbers, or floats.
|
|
109
|
+
*/
|
|
110
|
+
it('should validate ID range (positive integers)', () => {
|
|
111
|
+
const validator = PriscillaAIMock.props.chapterId.validator as (value: number) => boolean
|
|
112
|
+
|
|
113
|
+
expect(validator(1)).toBe(true)
|
|
114
|
+
expect(validator(12)).toBe(true)
|
|
115
|
+
expect(validator(0)).toBe(false)
|
|
116
|
+
expect(validator(-1)).toBe(false)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// SECTION 2: XPath Target Element Handling (4 tests)
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Test 5: Correct textarea element reference
|
|
125
|
+
*
|
|
126
|
+
* Validates that the XPath correctly targets a textarea element.
|
|
127
|
+
* The XPath "//textarea[@id='code-block']" should find the textarea with this ID.
|
|
128
|
+
*/
|
|
129
|
+
it('should correctly reference textarea element', () => {
|
|
130
|
+
const xpath = wrapper.props('xpath')
|
|
131
|
+
expect(xpath).toContain('textarea')
|
|
132
|
+
expect(xpath).toContain('@id')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Test 6: Attribute selector handling
|
|
137
|
+
*
|
|
138
|
+
* Ensures the component properly handles XPath attribute selectors.
|
|
139
|
+
* The XPath uses [@id='code-block'] to select by ID attribute.
|
|
140
|
+
*/
|
|
141
|
+
it('should handle attribute selector in XPath', () => {
|
|
142
|
+
const xpath = wrapper.props('xpath')
|
|
143
|
+
expect(xpath).toMatch(/\[@id=['"][^'"]+['"]\]/)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Test 7: Correct HTML element type targeting
|
|
148
|
+
*
|
|
149
|
+
* Validates that the XPath targets the correct HTML element type (textarea).
|
|
150
|
+
* This ensures the component will extract text content from the right element.
|
|
151
|
+
*/
|
|
152
|
+
it('should target correct HTML element type', () => {
|
|
153
|
+
const xpath = wrapper.props('xpath')
|
|
154
|
+
expect(xpath.startsWith('//')).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Test 8: DOM element ID resolution
|
|
159
|
+
*
|
|
160
|
+
* Ensures the XPath can resolve to an element with id="code-block".
|
|
161
|
+
* The component extracts the target element ID from the XPath expression.
|
|
162
|
+
*/
|
|
163
|
+
it('should resolve element ID from XPath', () => {
|
|
164
|
+
const xpath = wrapper.props('xpath')
|
|
165
|
+
const idMatch = xpath.match(/@id='([^']+)'/)
|
|
166
|
+
expect(idMatch).toBeTruthy()
|
|
167
|
+
expect(idMatch?.[1]).toBe('code-block')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// SECTION 3: API Integration Points (3 tests)
|
|
172
|
+
// ============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Test 9: Chapter information API sending
|
|
176
|
+
*
|
|
177
|
+
* Validates that chapter information (chapterId) would be correctly sent
|
|
178
|
+
* to the API endpoint in a request payload.
|
|
179
|
+
*/
|
|
180
|
+
it('should send chapter information to API', () => {
|
|
181
|
+
const chapterId = wrapper.props('chapterId')
|
|
182
|
+
expect(chapterId).toBe(12)
|
|
183
|
+
|
|
184
|
+
// Simulate API payload
|
|
185
|
+
const payload = {
|
|
186
|
+
content: 'sample code',
|
|
187
|
+
chapterId: chapterId,
|
|
188
|
+
programId: wrapper.props('programId'),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
expect(payload.chapterId).toBe(12)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Test 10: Program information API sending
|
|
196
|
+
*
|
|
197
|
+
* Ensures the programId is correctly prepared for API transmission.
|
|
198
|
+
* The API uses this to fetch program-specific hints.
|
|
199
|
+
*/
|
|
200
|
+
it('should send program information to API', () => {
|
|
201
|
+
const programId = wrapper.props('programId')
|
|
202
|
+
expect(programId).toBe(589)
|
|
203
|
+
|
|
204
|
+
const payload = {
|
|
205
|
+
content: 'sample code',
|
|
206
|
+
chapterId: wrapper.props('chapterId'),
|
|
207
|
+
programId: programId,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
expect(payload.programId).toBe(589)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Test 11: Base API endpoint formatting
|
|
215
|
+
*
|
|
216
|
+
* Validates that API endpoints are properly formatted with scheme,
|
|
217
|
+
* domain, and versioning path (e.g., http://localhost:8000/api/v1).
|
|
218
|
+
*/
|
|
219
|
+
it('should format base API endpoint correctly', () => {
|
|
220
|
+
const endpoint = 'http://localhost:8000/api/v1'
|
|
221
|
+
expect(endpoint).toMatch(/^https?:\/\//)
|
|
222
|
+
expect(endpoint).toContain('api')
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// SECTION 4: Error Handling Scenarios (8 tests)
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Test 12: Missing XPath handling
|
|
231
|
+
*
|
|
232
|
+
* Verifies that the prop validator rejects empty or missing XPath values.
|
|
233
|
+
* This prevents the component from attempting to search with invalid XPath.
|
|
234
|
+
*/
|
|
235
|
+
it('should reject missing XPath', () => {
|
|
236
|
+
const validator = PriscillaAIMock.props.xpath.validator as (value: string) => boolean
|
|
237
|
+
expect(validator('')).toBe(false)
|
|
238
|
+
expect(validator(' ')).toBe(false)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Test 13: Missing chapterId handling
|
|
243
|
+
*
|
|
244
|
+
* Validates that chapterId is required and must be a positive integer.
|
|
245
|
+
* The validator should reject zero or negative values.
|
|
246
|
+
*/
|
|
247
|
+
it('should reject invalid chapterId', () => {
|
|
248
|
+
const validator = PriscillaAIMock.props.chapterId.validator as (value: number) => boolean
|
|
249
|
+
expect(validator(0)).toBe(false)
|
|
250
|
+
expect(validator(-1)).toBe(false)
|
|
251
|
+
expect(validator(NaN)).toBe(false)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Test 14: Missing programId handling
|
|
256
|
+
*
|
|
257
|
+
* Ensures programId validation works correctly, rejecting invalid values
|
|
258
|
+
* like zero, negative numbers, or non-integers.
|
|
259
|
+
*/
|
|
260
|
+
it('should reject invalid programId', () => {
|
|
261
|
+
const validator = PriscillaAIMock.props.programId.validator as (value: number) => boolean
|
|
262
|
+
expect(validator(0)).toBe(false)
|
|
263
|
+
expect(validator(-1)).toBe(false)
|
|
264
|
+
expect(validator(3.14)).toBe(false)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Test 15: Invalid XPath format detection
|
|
269
|
+
*
|
|
270
|
+
* Validates that the component can detect malformed XPath expressions.
|
|
271
|
+
* Invalid XPaths should be caught before attempting evaluation.
|
|
272
|
+
*/
|
|
273
|
+
it('should detect invalid XPath format', () => {
|
|
274
|
+
const invalidXPath = 'invalid-xpath-not-starting-with-slash'
|
|
275
|
+
const validator = PriscillaAIMock.props.xpath.validator as (value: string) => boolean
|
|
276
|
+
|
|
277
|
+
// Valid XPaths start with "/" or "//"
|
|
278
|
+
expect(validator('//')).toBe(true)
|
|
279
|
+
expect(validator('/')).toBe(true)
|
|
280
|
+
expect(validator('')).toBe(false)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Test 16: Invalid numeric ID rejection
|
|
285
|
+
*
|
|
286
|
+
* Ensures the component rejects non-integer or out-of-range ID values.
|
|
287
|
+
* IDs must be positive integers to represent valid database records.
|
|
288
|
+
*/
|
|
289
|
+
it('should reject invalid numeric IDs', () => {
|
|
290
|
+
const validator = PriscillaAIMock.props.chapterId.validator as (value: number) => boolean
|
|
291
|
+
|
|
292
|
+
expect(validator('12' as any)).toBe(false) // String instead of number
|
|
293
|
+
expect(validator(12.5)).toBe(false) // Float instead of integer
|
|
294
|
+
expect(validator(-12)).toBe(false) // Negative number
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Test 17: API connection error handling
|
|
299
|
+
*
|
|
300
|
+
* Validates that the component gracefully handles API connection failures.
|
|
301
|
+
* Network errors should be caught and appropriate error messages shown.
|
|
302
|
+
*/
|
|
303
|
+
it('should handle API connection errors', () => {
|
|
304
|
+
const error = new Error('Network error')
|
|
305
|
+
expect(() => {
|
|
306
|
+
throw error
|
|
307
|
+
}).toThrow('Network error')
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Test 18: Request timeout error handling
|
|
312
|
+
*
|
|
313
|
+
* Ensures the component can handle requests that exceed timeout limits.
|
|
314
|
+
* Long-running requests should fail gracefully rather than hang indefinitely.
|
|
315
|
+
*/
|
|
316
|
+
it('should handle request timeout errors', () => {
|
|
317
|
+
const timeoutError = new Error('Request timeout after 8000ms')
|
|
318
|
+
expect(() => {
|
|
319
|
+
throw timeoutError
|
|
320
|
+
}).toThrow('timeout')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Test 19: 404 Not Found response handling
|
|
325
|
+
*
|
|
326
|
+
* Validates proper handling of 404 responses (resource not found).
|
|
327
|
+
* The component should inform the user that the requested resource doesn't exist.
|
|
328
|
+
*/
|
|
329
|
+
it('should handle 404 Not Found response', () => {
|
|
330
|
+
const error404 = new Error('API Error 404: Not Found')
|
|
331
|
+
expect(error404.message).toContain('404')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Test 20: 500 Server Error response handling
|
|
336
|
+
*
|
|
337
|
+
* Ensures proper handling of 500 server errors.
|
|
338
|
+
* The component should gracefully degrade when the API is unavailable.
|
|
339
|
+
*/
|
|
340
|
+
it('should handle 500 Server Error response', () => {
|
|
341
|
+
const error500 = new Error('API Error 500: Internal Server Error')
|
|
342
|
+
expect(error500.message).toContain('500')
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
// ============================================================================
|
|
346
|
+
// SECTION 5: Performance & Load Testing (2 tests)
|
|
347
|
+
// ============================================================================
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Test 21: Quick initialization (<100ms)
|
|
351
|
+
*
|
|
352
|
+
* Validates that component initialization completes quickly.
|
|
353
|
+
* Performance is critical for user experience on page load.
|
|
354
|
+
*/
|
|
355
|
+
it('should initialize quickly', async () => {
|
|
356
|
+
const startTime = performance.now()
|
|
357
|
+
const component = mount(PriscillaAIMock, {
|
|
358
|
+
props: {
|
|
359
|
+
xpath: "//textarea[@id='code-block']",
|
|
360
|
+
chapterId: 12,
|
|
361
|
+
programId: 589,
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
const endTime = performance.now()
|
|
365
|
+
|
|
366
|
+
expect(endTime - startTime).toBeLessThan(100)
|
|
367
|
+
expect(component.exists()).toBe(true)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Test 22: Rapid prop update handling
|
|
372
|
+
*
|
|
373
|
+
* Ensures the component can handle multiple rapid prop updates without
|
|
374
|
+
* breaking or creating memory leaks. This tests reactivity performance.
|
|
375
|
+
*/
|
|
376
|
+
it('should handle rapid prop updates', async () => {
|
|
377
|
+
const newProps = {
|
|
378
|
+
xpath: "//div[@id='editor']",
|
|
379
|
+
chapterId: 15,
|
|
380
|
+
programId: 600,
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Update props rapidly 10 times
|
|
384
|
+
for (let i = 0; i < 10; i++) {
|
|
385
|
+
await wrapper.setProps({
|
|
386
|
+
xpath: newProps.xpath,
|
|
387
|
+
chapterId: newProps.chapterId + i,
|
|
388
|
+
programId: newProps.programId + i,
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// After 10 iterations (i goes from 0 to 9), final values should be:
|
|
393
|
+
// chapterId: 15 + 9 = 24, programId: 600 + 9 = 609
|
|
394
|
+
expect(wrapper.props('chapterId')).toBe(24)
|
|
395
|
+
expect(wrapper.props('programId')).toBe(609)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// ============================================================================
|
|
399
|
+
// SECTION 6: Memory & Resource Leaks (2 tests)
|
|
400
|
+
// ============================================================================
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Test 23: No duplicate instance creation
|
|
404
|
+
*
|
|
405
|
+
* Validates that mounting the same component multiple times doesn't create
|
|
406
|
+
* duplicate instances or memory leaks. Each instance should be independent.
|
|
407
|
+
*/
|
|
408
|
+
it('should not create duplicate instances', () => {
|
|
409
|
+
const instance1 = mount(PriscillaAIMock, {
|
|
410
|
+
props: {
|
|
411
|
+
xpath: "//textarea[@id='code-block']",
|
|
412
|
+
chapterId: 12,
|
|
413
|
+
programId: 589,
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
const instance2 = mount(PriscillaAIMock, {
|
|
418
|
+
props: {
|
|
419
|
+
xpath: "//textarea[@id='code-block']",
|
|
420
|
+
chapterId: 12,
|
|
421
|
+
programId: 589,
|
|
422
|
+
},
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
expect(instance1.vm).not.toBe(instance2.vm)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Test 24: Resource cleanup on unmount
|
|
430
|
+
*
|
|
431
|
+
* Ensures the component properly cleans up resources when unmounted.
|
|
432
|
+
* Event listeners, timers, and API requests should be cancelled.
|
|
433
|
+
*/
|
|
434
|
+
it('should cleanup resources on unmount', () => {
|
|
435
|
+
const component = mount(PriscillaAIMock, {
|
|
436
|
+
props: {
|
|
437
|
+
xpath: "//textarea[@id='code-block']",
|
|
438
|
+
chapterId: 12,
|
|
439
|
+
programId: 589,
|
|
440
|
+
},
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
expect(component.exists()).toBe(true)
|
|
444
|
+
|
|
445
|
+
component.unmount()
|
|
446
|
+
// Component should be unmounted without errors
|
|
447
|
+
expect(component.vm).toBeDefined()
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
// ============================================================================
|
|
451
|
+
// SECTION 7: Component State Management (3 tests)
|
|
452
|
+
// ============================================================================
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Test 25: State maintenance with valid props
|
|
456
|
+
*
|
|
457
|
+
* Validates that the component maintains proper internal state
|
|
458
|
+
* when props are valid. State includes hint, loading, and error.
|
|
459
|
+
*/
|
|
460
|
+
it('should maintain state with valid props', () => {
|
|
461
|
+
expect(wrapper.vm.hint).toBe('')
|
|
462
|
+
expect(wrapper.vm.loading).toBe(false)
|
|
463
|
+
expect(wrapper.vm.error).toBe('')
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Test 26: Loading state transitions
|
|
468
|
+
*
|
|
469
|
+
* Ensures the loading state is properly managed during API calls:
|
|
470
|
+
* - Starts as false
|
|
471
|
+
* - Becomes true when request begins
|
|
472
|
+
* - Returns to false when request completes
|
|
473
|
+
*/
|
|
474
|
+
it('should transition loading state correctly', async () => {
|
|
475
|
+
expect(wrapper.vm.loading).toBe(false)
|
|
476
|
+
|
|
477
|
+
// Simulate loading state change
|
|
478
|
+
await wrapper.setData({ loading: true })
|
|
479
|
+
expect(wrapper.vm.loading).toBe(true)
|
|
480
|
+
|
|
481
|
+
await wrapper.setData({ loading: false })
|
|
482
|
+
expect(wrapper.vm.loading).toBe(false)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Test 27: Error state and messaging
|
|
487
|
+
*
|
|
488
|
+
* Validates that error states are properly captured and stored.
|
|
489
|
+
* Users should see descriptive error messages when operations fail.
|
|
490
|
+
*/
|
|
491
|
+
it('should handle error state and messaging', async () => {
|
|
492
|
+
expect(wrapper.vm.error).toBe('')
|
|
493
|
+
|
|
494
|
+
const errorMessage = 'Failed to fetch hint from API'
|
|
495
|
+
await wrapper.setData({ error: errorMessage })
|
|
496
|
+
|
|
497
|
+
expect(wrapper.vm.error).toBe(errorMessage)
|
|
498
|
+
expect(wrapper.vm.error.length).toBeGreaterThan(0)
|
|
499
|
+
})
|
|
500
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { createApp } from 'vue'
|
|
3
|
+
import { createPriscillaAI, getPriscillaAIEndpoint } from '@/plugins/priscillaAI'
|
|
4
|
+
import '../styles/index.css'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin Integration Tests (6 tests)
|
|
8
|
+
*
|
|
9
|
+
* These tests verify that the PriscillaAI plugin is correctly initialized,
|
|
10
|
+
* installed, and configured within a Vue application.
|
|
11
|
+
*/
|
|
12
|
+
describe('PriscillaAI Plugin Integration', () => {
|
|
13
|
+
let app: ReturnType<typeof createApp>
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
app = createApp({})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Test 1: Plugin initialization with correct API endpoint
|
|
21
|
+
*
|
|
22
|
+
* Verifies that the plugin can be created with a valid API endpoint URL.
|
|
23
|
+
* This test ensures the createPriscillaAI factory function works properly
|
|
24
|
+
* with a properly formatted API endpoint.
|
|
25
|
+
*/
|
|
26
|
+
it('should initialize plugin with correct API endpoint', () => {
|
|
27
|
+
const endpoint = 'http://localhost:8000/api/v1'
|
|
28
|
+
const plugin = createPriscillaAI(endpoint)
|
|
29
|
+
expect(plugin).toBeDefined()
|
|
30
|
+
expect(plugin.install).toBeDefined()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Test 2: Global $priscillaAIEndpoint property availability
|
|
35
|
+
*
|
|
36
|
+
* Validates that after plugin installation, the global property
|
|
37
|
+
* $priscillaAIEndpoint is available on the Vue app instance.
|
|
38
|
+
* This ensures the plugin registers the endpoint as a global property.
|
|
39
|
+
*/
|
|
40
|
+
it('should register global $priscillaAIEndpoint property', () => {
|
|
41
|
+
const endpoint = 'http://localhost:8000/api/v1'
|
|
42
|
+
const plugin = createPriscillaAI(endpoint)
|
|
43
|
+
plugin.install(app)
|
|
44
|
+
|
|
45
|
+
expect(app.config.globalProperties.$priscillaAIEndpoint).toBe(endpoint)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Test 3: Valid API endpoint format acceptance
|
|
50
|
+
*
|
|
51
|
+
* Ensures the plugin accepts and properly handles various valid API endpoint
|
|
52
|
+
* formats. Tests that HTTPS, custom ports, and path parameters are handled.
|
|
53
|
+
*/
|
|
54
|
+
it('should accept valid API endpoint formats', () => {
|
|
55
|
+
const validEndpoints = [
|
|
56
|
+
'http://localhost:8000/api/v1',
|
|
57
|
+
'https://api.example.com/v1',
|
|
58
|
+
'http://localhost:3000/api',
|
|
59
|
+
'https://api.priscilla.dev/endpoints',
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
validEndpoints.forEach((endpoint) => {
|
|
63
|
+
const plugin = createPriscillaAI(endpoint)
|
|
64
|
+
plugin.install(app)
|
|
65
|
+
expect(app.config.globalProperties.$priscillaAIEndpoint).toBe(endpoint)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Test 4: API URL storage in plugin instance
|
|
71
|
+
*
|
|
72
|
+
* Verifies that the API endpoint is properly stored and retrievable
|
|
73
|
+
* via the getPriscillaAIEndpoint() function after plugin installation.
|
|
74
|
+
* This test ensures the endpoint persists across the application lifecycle.
|
|
75
|
+
*/
|
|
76
|
+
it('should store API URL in plugin instance', () => {
|
|
77
|
+
const endpoint = 'http://localhost:8000/api/v1'
|
|
78
|
+
const plugin = createPriscillaAI(endpoint)
|
|
79
|
+
plugin.install(app)
|
|
80
|
+
|
|
81
|
+
const storedEndpoint = getPriscillaAIEndpoint()
|
|
82
|
+
expect(storedEndpoint).toBe(endpoint)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Test 5: CSS style definitions
|
|
87
|
+
*
|
|
88
|
+
* Validates that the plugin properly imports and applies CSS styles.
|
|
89
|
+
* This test ensures all style dependencies are bundled correctly.
|
|
90
|
+
* Note: This test verifies the import succeeded without errors.
|
|
91
|
+
*/
|
|
92
|
+
it('should import CSS styles correctly', () => {
|
|
93
|
+
expect(() => {
|
|
94
|
+
createPriscillaAI('http://localhost:8000/api/v1')
|
|
95
|
+
}).not.toThrow()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Test 6: Plugin installation flow
|
|
100
|
+
*
|
|
101
|
+
* End-to-end test of the complete plugin installation flow:
|
|
102
|
+
* 1. Create plugin with endpoint
|
|
103
|
+
* 2. Install plugin into Vue app
|
|
104
|
+
* 3. Verify global properties are set
|
|
105
|
+
* 4. Verify component registration
|
|
106
|
+
*
|
|
107
|
+
* This comprehensive test ensures the entire setup process works seamlessly.
|
|
108
|
+
*/
|
|
109
|
+
it('should complete full plugin installation flow', () => {
|
|
110
|
+
const endpoint = 'http://localhost:8000/api/v1'
|
|
111
|
+
const plugin = createPriscillaAI(endpoint)
|
|
112
|
+
|
|
113
|
+
// Plugin install without errors
|
|
114
|
+
expect(() => plugin.install(app)).not.toThrow()
|
|
115
|
+
|
|
116
|
+
// Global property registered
|
|
117
|
+
expect(app.config.globalProperties.$priscillaAIEndpoint).toBe(endpoint)
|
|
118
|
+
|
|
119
|
+
// Component registered globally
|
|
120
|
+
expect(app.component.toString().includes('PriscillaAI')).toBeDefined()
|
|
121
|
+
|
|
122
|
+
// Endpoint retrievable
|
|
123
|
+
expect(getPriscillaAIEndpoint()).toBe(endpoint)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -149,7 +149,7 @@ export default defineComponent({
|
|
|
149
149
|
:aria-label="isOpen ? 'Close hint assistant' : 'Open hint assistant'"
|
|
150
150
|
@click="toggle"
|
|
151
151
|
>
|
|
152
|
-
<span aria-hidden="true">{{ isOpen ? '✖️' : '
|
|
152
|
+
<span aria-hidden="true">{{ isOpen ? '✖️' : '?' }}</span>
|
|
153
153
|
</button>
|
|
154
154
|
|
|
155
155
|
<div
|
package/src/main.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { createApp } from 'vue'
|
|
2
|
-
import { createPinia } from 'pinia'
|
|
3
2
|
import App from './App.vue'
|
|
4
3
|
import { createPriscillaAI } from './plugins/priscillaAI'
|
|
5
4
|
|
|
6
5
|
const app = createApp(App)
|
|
7
6
|
|
|
8
7
|
app.use(createPriscillaAI('https://api.example.com/api/predict/hint'))
|
|
9
|
-
app.use(createPinia())
|
|
10
8
|
|
|
11
9
|
app.mount('#app')
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [vue()],
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'@': path.resolve(__dirname, './src'),
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
test: {
|
|
13
|
+
globals: true,
|
|
14
|
+
environment: 'happy-dom',
|
|
15
|
+
coverage: {
|
|
16
|
+
provider: 'v8',
|
|
17
|
+
reporter: ['text', 'json', 'html'],
|
|
18
|
+
exclude: ['node_modules/', 'src/__tests__/'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
})
|