@priscilla-ai/vue 1.0.6 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.7] - 12-04-2026
4
+ - Added comprehensive test suite with 50 unit and integration tests
5
+ - Tests cover plugin initialization, component integration, props validation, error handling, performance, and memory management
6
+ - Configured Vitest with Vue Test Utils and happy-dom
7
+ - Added test scripts: test, test:run, test:ui, test:coverage
8
+ - All tests use standard CSS (no Tailwind dependencies)
9
+
10
+ ## [1.0.6] - 11-04-2026
11
+ - Minor fixes and improvements
12
+
3
13
  ## [1.0.0] - 18-03-2026
4
14
  - First Release
5
15
  - Vue 3 plugin for AI-powered code
@@ -0,0 +1,604 @@
1
+ # PriscillaAI Component - Implementation Fixes & Recommendations
2
+
3
+ ## Quick Reference: Issues Found & Solutions
4
+
5
+ ### 🔴 CRITICAL ISSUES (Do First)
6
+
7
+ #### Issue 1: No Element Existence Validation
8
+ **Problem:** Component assumes the XPath target element exists
9
+ **Risk:** Silent failures, non-functional component
10
+ **Solution Level:** HIGH PRIORITY
11
+
12
+ ```javascript
13
+ // BEFORE (Current - Problematic)
14
+ mounted() {
15
+ const element = document.evaluate(this.xpath, document);
16
+ // No check if element actually exists!
17
+ this.bindToElement(element);
18
+ }
19
+
20
+ // AFTER (Fixed)
21
+ mounted() {
22
+ if (!this.validateTargetElement()) {
23
+ this.handleErrorState('Target element not found at XPath: ' + this.xpath);
24
+ return;
25
+ }
26
+ this.bindToElement(this.getTargetElement());
27
+ }
28
+
29
+ validateTargetElement() {
30
+ try {
31
+ const result = document.evaluate(
32
+ this.xpath,
33
+ document,
34
+ null,
35
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
36
+ null
37
+ );
38
+ return result.singleNodeValue !== null;
39
+ } catch (error) {
40
+ console.error('Invalid XPath:', error);
41
+ return false;
42
+ }
43
+ }
44
+
45
+ getTargetElement() {
46
+ const result = document.evaluate(
47
+ this.xpath,
48
+ document,
49
+ null,
50
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
51
+ null
52
+ );
53
+ return result.singleNodeValue;
54
+ }
55
+ ```
56
+
57
+ #### Issue 2: No Error Logging or User Feedback
58
+ **Problem:** API errors and failures are silent
59
+ **Risk:** Difficult debugging, poor user experience
60
+ **Solution Level:** HIGH PRIORITY
61
+
62
+ ```javascript
63
+ // Add to component data
64
+ data() {
65
+ return {
66
+ errors: [],
67
+ warnings: [],
68
+ lastErrorTime: null,
69
+ errorRecoveryAttempts: 0,
70
+ };
71
+ }
72
+
73
+ // Add error handler
74
+ handleError(context, error) {
75
+ const errorEntry = {
76
+ timestamp: new Date().toISOString(),
77
+ context,
78
+ message: error.message,
79
+ stack: error.stack,
80
+ };
81
+
82
+ this.errors.push(errorEntry);
83
+ this.lastErrorTime = Date.now();
84
+
85
+ // Log to console in development
86
+ if (process.env.NODE_ENV === 'development') {
87
+ console.error(`[PriscillaAI] ${context}:`, error);
88
+ }
89
+
90
+ // Send to error tracking service (Sentry, LogRocket, etc.)
91
+ if (window.__errorTracker) {
92
+ window.__errorTracker.captureException(error, {
93
+ tags: { component: 'PriscillaAI', context },
94
+ });
95
+ }
96
+
97
+ // Emit error event for parent component
98
+ this.$emit('error', errorEntry);
99
+ }
100
+
101
+ // Update API calls to use error handler
102
+ async initializeAPI() {
103
+ try {
104
+ const response = await fetch(`${this.apiUrl}/chapters/${this.chapterId}`);
105
+ if (!response.ok) {
106
+ throw new Error(`API Error ${response.status}: ${response.statusText}`);
107
+ }
108
+ return await response.json();
109
+ } catch (error) {
110
+ this.handleError('API Initialization', error);
111
+ return null;
112
+ }
113
+ }
114
+ ```
115
+
116
+ #### Issue 3: Missing Prop Type Validation
117
+ **Problem:** Component accepts any prop type
118
+ **Risk:** Runtime errors from invalid data
119
+ **Solution Level:** HIGH PRIORITY
120
+
121
+ ```javascript
122
+ // Update props definition
123
+ props: {
124
+ xpath: {
125
+ type: String,
126
+ required: true,
127
+ validator(value) {
128
+ if (!value) {
129
+ console.warn('[PriscillaAI] xpath prop is required and cannot be empty');
130
+ return false;
131
+ }
132
+ if (!value.startsWith('//') && !value.startsWith('/')) {
133
+ console.warn('[PriscillaAI] xpath must be a valid XPath expression starting with "/" or "//"');
134
+ return false;
135
+ }
136
+ return true;
137
+ },
138
+ },
139
+ chapterId: {
140
+ type: Number,
141
+ required: true,
142
+ validator(value) {
143
+ if (!Number.isInteger(value)) {
144
+ console.warn('[PriscillaAI] chapterId must be an integer');
145
+ return false;
146
+ }
147
+ if (value <= 0) {
148
+ console.warn('[PriscillaAI] chapterId must be a positive number');
149
+ return false;
150
+ }
151
+ return true;
152
+ },
153
+ },
154
+ programId: {
155
+ type: Number,
156
+ required: true,
157
+ validator(value) {
158
+ if (!Number.isInteger(value)) {
159
+ console.warn('[PriscillaAI] programId must be an integer');
160
+ return false;
161
+ }
162
+ if (value <= 0) {
163
+ console.warn('[PriscillaAI] programId must be a positive number');
164
+ return false;
165
+ }
166
+ return true;
167
+ },
168
+ },
169
+ apiUrl: {
170
+ type: String,
171
+ default: 'http://localhost:8000/api/v1',
172
+ validator(value) {
173
+ try {
174
+ new URL(value);
175
+ return true;
176
+ } catch {
177
+ console.warn('[PriscillaAI] apiUrl must be a valid URL');
178
+ return false;
179
+ }
180
+ },
181
+ },
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ### 🟠 HIGH PRIORITY FEATURES (Implement Next)
188
+
189
+ #### Feature 1: API Request Timeout
190
+ **Problem:** API calls can hang indefinitely on slow networks
191
+ **Impact:** Page becomes unresponsive
192
+
193
+ ```javascript
194
+ // Add timeout utility
195
+ const createTimeoutPromise = (ms) => new Promise((_, reject) =>
196
+ setTimeout(() => reject(new Error(`Request timeout after ${ms}ms`)), ms)
197
+ );
198
+
199
+ const fetchWithTimeout = async (url, options = {}) => {
200
+ const timeout = options.timeout || 8000; // 8 second default
201
+
202
+ return Promise.race([
203
+ fetch(url, {
204
+ ...options,
205
+ signal: AbortSignal.timeout(timeout),
206
+ }),
207
+ createTimeoutPromise(timeout),
208
+ ]);
209
+ };
210
+
211
+ // Use in component
212
+ async initializeAPI() {
213
+ try {
214
+ const response = await fetchWithTimeout(
215
+ `${this.apiUrl}/chapters/${this.chapterId}`,
216
+ { timeout: this.requestTimeout || 8000 }
217
+ );
218
+ if (!response.ok) {
219
+ throw new Error(`API Error: ${response.status}`);
220
+ }
221
+ return await response.json();
222
+ } catch (error) {
223
+ if (error.name === 'AbortError' || error.message.includes('timeout')) {
224
+ this.handleError('API Timeout', new Error('Request took too long'));
225
+ this.retryWithBackoff();
226
+ } else {
227
+ this.handleError('API Request', error);
228
+ }
229
+ }
230
+ }
231
+
232
+ // Add retry with exponential backoff
233
+ async retryWithBackoff(attempt = 1, maxAttempts = 3) {
234
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
235
+
236
+ if (attempt > maxAttempts) {
237
+ this.handleError('Max Retries', new Error('Failed after 3 attempts'));
238
+ return false;
239
+ }
240
+
241
+ console.info(`[PriscillaAI] Retrying API call (attempt ${attempt}/${maxAttempts})`);
242
+
243
+ await new Promise(resolve => setTimeout(resolve, delay));
244
+
245
+ const success = await this.initializeAPI();
246
+ if (!success) {
247
+ return this.retryWithBackoff(attempt + 1, maxAttempts);
248
+ }
249
+ return true;
250
+ }
251
+ ```
252
+
253
+ #### Feature 2: Loading State & User Feedback
254
+ **Problem:** No visual feedback during initialization
255
+ **Impact:** Users think component is broken
256
+
257
+ ```javascript
258
+ // Add to data
259
+ data() {
260
+ return {
261
+ isLoading: false,
262
+ isInitialized: false,
263
+ hasError: false,
264
+ errorMessage: '',
265
+ loadingMessage: 'Initializing code editor...',
266
+ };
267
+ }
268
+
269
+ // Add to template
270
+ <template>
271
+ <div class="priscilla-ai-wrapper">
272
+ <!-- Loading State -->
273
+ <div v-if="isLoading" class="priscilla-ai-loading">
274
+ <div class="spinner"></div>
275
+ <p class="loading-text">{{ loadingMessage }}</p>
276
+ </div>
277
+
278
+ <!-- Error State -->
279
+ <div v-if="hasError" class="priscilla-ai-error">
280
+ <div class="error-icon">⚠️</div>
281
+ <p class="error-message">{{ errorMessage }}</p>
282
+ <button @click="retry" class="retry-button">Try Again</button>
283
+ </div>
284
+
285
+ <!-- Success State (component renders here) -->
286
+ <div v-if="isInitialized && !hasError" class="priscilla-component-container">
287
+ <!-- PriscillaAI component content -->
288
+ </div>
289
+ </div>
290
+ </template>
291
+
292
+ // Add styling
293
+ <style scoped>
294
+ .priscilla-ai-loading {
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ padding: 40px 20px;
299
+ text-align: center;
300
+ }
301
+
302
+ .spinner {
303
+ width: 40px;
304
+ height: 40px;
305
+ border: 4px solid #f3f3f3;
306
+ border-top: 4px solid #3498db;
307
+ border-radius: 50%;
308
+ animation: spin 1s linear infinite;
309
+ }
310
+
311
+ @keyframes spin {
312
+ 0% { transform: rotate(0deg); }
313
+ 100% { transform: rotate(360deg); }
314
+ }
315
+
316
+ .priscilla-ai-error {
317
+ background: #fee;
318
+ border: 2px solid #fcc;
319
+ border-radius: 8px;
320
+ padding: 20px;
321
+ margin: 10px 0;
322
+ }
323
+
324
+ .error-icon {
325
+ font-size: 32px;
326
+ margin-bottom: 10px;
327
+ }
328
+
329
+ .error-message {
330
+ color: #c33;
331
+ margin: 10px 0;
332
+ }
333
+
334
+ .retry-button {
335
+ background: #3498db;
336
+ color: white;
337
+ border: none;
338
+ padding: 8px 16px;
339
+ border-radius: 4px;
340
+ cursor: pointer;
341
+ }
342
+
343
+ .retry-button:hover {
344
+ background: #2980b9;
345
+ }
346
+ </style>
347
+
348
+ // Add lifecycle methods
349
+ mounted() {
350
+ this.isLoading = true;
351
+ this.loadingMessage = 'Initializing code editor...';
352
+ this.initialize();
353
+ }
354
+
355
+ methods: {
356
+ async initialize() {
357
+ try {
358
+ this.isLoading = true;
359
+
360
+ // Validate target element
361
+ if (!this.validateTargetElement()) {
362
+ throw new Error(`Target element not found: ${this.xpath}`);
363
+ }
364
+
365
+ // Initialize API
366
+ await this.initializeAPI();
367
+
368
+ this.isInitialized = true;
369
+ this.$emit('ready');
370
+ } catch (error) {
371
+ this.handleError('Initialization', error);
372
+ this.hasError = true;
373
+ this.errorMessage = error.message;
374
+ } finally {
375
+ this.isLoading = false;
376
+ }
377
+ },
378
+
379
+ async retry() {
380
+ this.hasError = false;
381
+ this.errorMessage = '';
382
+ await this.initialize();
383
+ },
384
+ }
385
+ ```
386
+
387
+ #### Feature 3: Dynamic Element Re-binding
388
+ **Problem:** Won't work if target element is added after component mounts
389
+ **Impact:** Breaks with dynamic content loading
390
+
391
+ ```javascript
392
+ // Add computed getter
393
+ computed: {
394
+ targetElement() {
395
+ try {
396
+ const result = document.evaluate(
397
+ this.xpath,
398
+ document,
399
+ null,
400
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
401
+ null
402
+ );
403
+ return result.singleNodeValue;
404
+ } catch {
405
+ return null;
406
+ }
407
+ },
408
+ }
409
+
410
+ // Add watchers
411
+ watch: {
412
+ xpath(newXpath) {
413
+ console.log('[PriscillaAI] XPath changed, re-binding...');
414
+ this.rebind();
415
+ },
416
+
417
+ targetElement(newElement, oldElement) {
418
+ if (!oldElement && newElement) {
419
+ console.log('[PriscillaAI] Target element appeared, binding...');
420
+ this.bindToNewElement();
421
+ }
422
+ },
423
+ }
424
+
425
+ // Add mutation observer
426
+ mounted() {
427
+ this.observeDOM();
428
+ }
429
+
430
+ methods: {
431
+ observeDOM() {
432
+ const config = {
433
+ childList: true,
434
+ subtree: true,
435
+ attributes: false,
436
+ };
437
+
438
+ this.observerCallback = () => {
439
+ // Check if target element appears
440
+ if (this.targetElement && !this.isBound) {
441
+ this.bind();
442
+ }
443
+ };
444
+
445
+ this.observer = new MutationObserver(this.observerCallback);
446
+ this.observer.observe(document.body, config);
447
+ },
448
+ }
449
+
450
+ beforeUnmount() {
451
+ if (this.observer) {
452
+ this.observer.disconnect();
453
+ }
454
+ }
455
+ ```
456
+
457
+ ---
458
+
459
+ ### 🟡 MEDIUM PRIORITY IMPROVEMENTS
460
+
461
+ #### Improvement 1: Flexible Configuration
462
+ **Location:** `src/components/Navbar.vue`
463
+
464
+ ```vue
465
+ <template>
466
+ <nav class="bg-green-700 border-b border-green-500">
467
+ <div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
468
+ <div class="flex h-20 items-center justify-between">
469
+ <div class="flex flex-1 items-center justify-center md:items-stretch md:justify-start">
470
+ <a class="flex flex-shrink-0 items-center mr-4" href="/">
471
+ <img class="h-10 w-auto" :src="logo" alt="Vue Jobs" />
472
+ <span class="hidden md:block text-white text-2xl font-bold ml-2">
473
+ Vue Jobs
474
+ </span>
475
+ </a>
476
+ <div class="md:ml-auto">
477
+ <div class="flex space-x-2">
478
+ <!-- Make props dynamic from route -->
479
+ <PriscillaAI
480
+ :xpath="currentXPath"
481
+ :chapterId="currentChapterId"
482
+ :programId="currentProgramId"
483
+ @ready="onEditorReady"
484
+ @error="onEditorError"
485
+ />
486
+ </div>
487
+ </div>
488
+ </div>
489
+ </div>
490
+ </div>
491
+ </nav>
492
+ </template>
493
+
494
+ <script setup>
495
+ import { computed } from 'vue';
496
+ import { useRoute } from 'vue-router';
497
+ import logo from '@/assets/img/logo.png';
498
+
499
+ const route = useRoute();
500
+
501
+ // Default selectors and IDs
502
+ const DEFAULT_XPATH = "//textarea[@id='code-block']";
503
+ const DEFAULT_CHAPTER_ID = 12;
504
+ const DEFAULT_PROGRAM_ID = 589;
505
+
506
+ // Computed properties from route params or defaults
507
+ const currentXPath = computed(() =>
508
+ route.query.xpath || DEFAULT_XPATH
509
+ );
510
+
511
+ const currentChapterId = computed(() =>
512
+ parseInt(route.query.chapterId) || DEFAULT_CHAPTER_ID
513
+ );
514
+
515
+ const currentProgramId = computed(() =>
516
+ parseInt(route.query.programId) || DEFAULT_PROGRAM_ID
517
+ );
518
+
519
+ const onEditorReady = () => {
520
+ console.log('[Navbar] PriscillaAI editor is ready');
521
+ };
522
+
523
+ const onEditorError = (errorEntry) => {
524
+ console.warn('[Navbar] Editor error:', errorEntry);
525
+ // Optionally show error notification to user
526
+ };
527
+ </script>
528
+ ```
529
+
530
+ #### Improvement 2: Test Expansion
531
+ **Add integration and e2e tests:**
532
+
533
+ ```bash
534
+ # Install additional testing tools
535
+ npm install --save-dev @testing-library/vue @testing-library/user-event cypress
536
+
537
+ # Create integration test
538
+ cat > src/__tests__/PriscillaAI.integration.spec.js << 'EOF'
539
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
540
+ import { mount } from '@vue/test-utils'
541
+ import { createApp } from 'vue'
542
+ import Navbar from '../components/Navbar.vue'
543
+
544
+ describe('PriscillaAI Integration Tests', () => {
545
+ // Test real API connectivity
546
+ // Test with actual DOM elements
547
+ // Test error recovery flows
548
+ })
549
+ EOF
550
+
551
+ # Update package.json
552
+ npm script additions:
553
+ "test": "vitest",
554
+ "test:integration": "vitest src/__tests__/*.integration.spec.js",
555
+ "test:e2e": "cypress open",
556
+ "test:coverage": "vitest run --coverage"
557
+ ```
558
+
559
+ ---
560
+
561
+ ## 📋 Implementation Checklist
562
+
563
+ - [ ] **Week 1: Critical Fixes**
564
+ - [ ] Add element validation to PriscillaAI component
565
+ - [ ] Implement error logging system
566
+ - [ ] Add prop type validators
567
+ - [ ] Test in staging environment
568
+ - [ ] Deploy to production
569
+
570
+ - [ ] **Week 2: High Priority Features**
571
+ - [ ] Implement request timeout handling
572
+ - [ ] Build loading state UI
573
+ - [ ] Add mutation observer for dynamic elements
574
+ - [ ] Create user-facing error messages
575
+ - [ ] Update end-to-end tests
576
+
577
+ - [ ] **Week 3: Medium Priority**
578
+ - [ ] Make props configurable from routes
579
+ - [ ] Add analytics integration
580
+ - [ ] Expand test coverage
581
+ - [ ] Create documentation
582
+
583
+ ---
584
+
585
+ ## 📊 Before & After Comparison
586
+
587
+ | Aspect | Before | After |
588
+ |--------|--------|-------|
589
+ | Element Validation | ❌ None | ✅ Comprehensive |
590
+ | Error Logging | ❌ Silent | ✅ Detailed |
591
+ | Type Safety | ⚠️ Partial | ✅ Full |
592
+ | Timeouts | ❌ No | ✅ 8s default |
593
+ | Loading UI | ❌ None | ✅ Spinner + Message |
594
+ | Dynamic Binding | ❌ No | ✅ Yes |
595
+ | Test Coverage | ⚠️ 50 tests | ✅ 80+ tests |
596
+
597
+ ---
598
+
599
+ ## Contact & Support
600
+
601
+ For questions about these recommendations:
602
+ - Review TEST_REPORT.md for full test details
603
+ - Check test files in src/__tests__/ for examples
604
+ - Consult this document for implementation guides
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@priscilla-ai/vue",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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
  }