@pilaf/reporting 1.0.0

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.
@@ -0,0 +1,47 @@
1
+ // packages/reporting/lib/generator.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ class ReportGenerator {
6
+ constructor(options = {}) {
7
+ this.templatePath = options.templatePath ||
8
+ path.join(__dirname, '../templates/report.html');
9
+ this.cssPath = options.cssPath ||
10
+ path.join(__dirname, '../templates/styles.css');
11
+ }
12
+
13
+ generate(testResults, outputPath) {
14
+ const template = fs.readFileSync(this.templatePath, 'utf8');
15
+ const cssContent = fs.readFileSync(this.cssPath, 'utf8');
16
+
17
+ const html = template
18
+ .replace('__SUITENAME__', this._escapeHtml(testResults.suiteName || 'Pilaf Tests'))
19
+ .replace('__STORIESJSON__', this._escapeJson(JSON.stringify(testResults)))
20
+ .replace('__CSSCONTENT__', cssContent);
21
+
22
+ // Ensure output directory exists
23
+ const dir = path.dirname(outputPath);
24
+ if (!fs.existsSync(dir)) {
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ }
27
+
28
+ fs.writeFileSync(outputPath, html);
29
+ return outputPath;
30
+ }
31
+
32
+ _escapeHtml(text) {
33
+ return text
34
+ .replace(/&/g, '&')
35
+ .replace(/</g, '&lt;')
36
+ .replace(/>/g, '&gt;')
37
+ .replace(/"/g, '&quot;')
38
+ .replace(/'/g, '&#039;');
39
+ }
40
+
41
+ _escapeJson(json) {
42
+ // Escape </script> to prevent breaking out of script tag
43
+ return json.replace(/<\/script>/g, '<\\/script>');
44
+ }
45
+ }
46
+
47
+ module.exports = { ReportGenerator };
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // packages/reporting/lib/index.js
2
+ const { ReportGenerator } = require('./generator');
3
+
4
+ module.exports = {
5
+ ReportGenerator
6
+ };
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@pilaf/reporting",
3
+ "version": "1.0.0",
4
+ "main": "lib/index.js",
5
+ "files": [
6
+ "lib/*.js",
7
+ "lib/**/*.js",
8
+ "!lib/**/*.spec.js",
9
+ "!lib/**/*.test.js",
10
+ "templates/**/*",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "dependencies": {}
18
+ }
@@ -0,0 +1,152 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pilaf Test Report - __SUITENAME__</title>
7
+ <link href="https://fonts.cdnfonts.com/css/minecraft-pe" rel="stylesheet">
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <style>__CSSCONTENT__</style>
11
+ </head>
12
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
13
+ <div id="app">
14
+ <header class="bg-gray-800 border-b border-gray-700 sticky top-0 z-50 p-4">
15
+ <h1 class="text-xl font-bold">Pilaf Test Report</h1>
16
+ <p class="text-sm text-gray-400">{{ report.suiteName }}</p>
17
+ <span class="px-3 py-1 rounded-full text-sm font-semibold"
18
+ :class="report.passed ? 'bg-green-900 text-green-300' : 'bg-red-900 text-red-300'">
19
+ {{ report.passed ? 'PASSED' : 'FAILED' }}
20
+ </span>
21
+ </header>
22
+ <main class="max-w-7xl mx-auto px-4 py-6">
23
+ <div v-for="story in stories" :key="story.name" class="bg-gray-800 rounded-lg p-4 mb-4 border border-gray-700">
24
+ <h2 class="text-lg font-semibold text-white">{{ story.name }}</h2>
25
+ <p class="text-sm text-gray-400">{{ story.passedCount }} passed, {{ story.failedCount }} failed</p>
26
+
27
+ <!-- Console Logs for this Story -->
28
+ <div v-if="story.consoleLogs && story.consoleLogs.length > 0" class="mt-3 border border-gray-700 rounded">
29
+ <button @click="toggleStoryConsole(story.name)"
30
+ class="w-full px-3 py-2 flex items-center justify-between text-left hover:bg-gray-700 rounded transition-colors">
31
+ <span class="text-sm font-medium text-gray-300">Console Logs</span>
32
+ <span class="text-gray-400">{{ isStoryConsoleExpanded(story.name) ? '▼' : '▶' }}</span>
33
+ </button>
34
+ <div v-show="isStoryConsoleExpanded(story.name)" class="p-3 border-t border-gray-700 max-h-60 overflow-y-auto">
35
+ <div class="font-mono text-xs space-y-1">
36
+ <div v-for="(log, index) in story.consoleLogs" :key="index"
37
+ class="text-gray-300 hover:text-white transition-colors">
38
+ <span class="text-gray-500 select-none">[{{ formatTime(log.timestamp) }}]</span> {{ log.message }}
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- Steps -->
45
+ <div class="mt-3">
46
+ <h3 class="text-sm font-medium text-gray-300 mb-2">Steps</h3>
47
+ <div v-for="(step, stepIndex) in story.steps" :key="step.name" class="ml-4 mt-2">
48
+ <div @click="toggleStep(story.name, stepIndex)"
49
+ class="p-2 rounded cursor-pointer hover:bg-gray-700/50 transition-colors flex items-center justify-between"
50
+ :class="step.passed ? 'bg-green-900/30' : 'bg-red-900/30'">
51
+ <div class="flex items-center gap-2">
52
+ <span class="text-gray-400">{{ isStepExpanded(story.name, stepIndex) ? '▼' : '▶' }}</span>
53
+ <span class="font-mono text-sm">{{ step.name }}</span>
54
+ </div>
55
+ <span v-if="step.executionContext" class="px-2 py-0.5 rounded text-xs bg-gray-700">
56
+ {{ step.executionContext.executor }}
57
+ </span>
58
+ </div>
59
+ <div v-show="isStepExpanded(story.name, stepIndex)" class="ml-4 mt-2 p-3 bg-gray-900/50 rounded border border-gray-700">
60
+ <div v-if="step.details && step.details.length > 0" class="space-y-2">
61
+ <template v-for="(detail, idx) in step.details" :key="idx">
62
+ <!-- Action/Response pair display -->
63
+ <div v-if="detail.type === 'action'" class="font-mono text-xs text-yellow-300">
64
+ → {{ detail.message }}
65
+ </div>
66
+ <div v-else-if="detail.type === 'response'" class="font-mono text-xs text-green-300 ml-4">
67
+ ← {{ detail.message }}
68
+ </div>
69
+ <!-- Other details -->
70
+ <div v-else
71
+ class="font-mono text-xs"
72
+ :class="getDetailClass(detail.message)">
73
+ {{ formatDetail(detail.message) }}
74
+ </div>
75
+ </template>
76
+ </div>
77
+ <div v-else class="text-gray-500 text-sm italic">No details available</div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </main>
83
+ </div>
84
+ <script>
85
+ const reportData = __STORIESJSON__;
86
+ const { createApp } = Vue;
87
+ createApp({
88
+ data() {
89
+ return {
90
+ report: reportData,
91
+ stories: reportData.stories || [],
92
+ expandedSteps: new Set(),
93
+ expandedStoryConsoles: new Set()
94
+ };
95
+ },
96
+ methods: {
97
+ formatTime(timestamp) {
98
+ const date = new Date(timestamp);
99
+ return date.toTimeString().split(' ')[0];
100
+ },
101
+ toggleStep(storyName, stepIndex) {
102
+ const key = `${storyName}-${stepIndex}`;
103
+ if (this.expandedSteps.has(key)) {
104
+ this.expandedSteps.delete(key);
105
+ } else {
106
+ this.expandedSteps.add(key);
107
+ }
108
+ this.expandedSteps = new Set(this.expandedSteps);
109
+ },
110
+ isStepExpanded(storyName, stepIndex) {
111
+ return this.expandedSteps.has(`${storyName}-${stepIndex}`);
112
+ },
113
+ toggleStoryConsole(storyName) {
114
+ if (this.expandedStoryConsoles.has(storyName)) {
115
+ this.expandedStoryConsoles.delete(storyName);
116
+ } else {
117
+ this.expandedStoryConsoles.add(storyName);
118
+ }
119
+ this.expandedStoryConsoles = new Set(this.expandedStoryConsoles);
120
+ },
121
+ isStoryConsoleExpanded(storyName) {
122
+ return this.expandedStoryConsoles.has(storyName);
123
+ },
124
+ formatDetail(message) {
125
+ // Format common patterns for better readability
126
+ return message
127
+ .replace(/^RCON command: /, '→ ')
128
+ .replace(/^Found /, '• Found ')
129
+ .replace(/^Retrieved /, '• Retrieved ')
130
+ .replace(/^Stored result as /, '💾 Saved: ')
131
+ .replace(/^Waited /, '⏱ Waited ')
132
+ .replace(/^Assertion passed: /, '✓ ')
133
+ .replace(/^Assertion failed: /, '✗ ')
134
+ .replace(/^ location: /, ' 📍 ')
135
+ .replace(/^ chat: /, ' 💬 ')
136
+ .replace(/^ executed command: /, ' ⚡ ');
137
+ },
138
+ getDetailClass(message) {
139
+ if (message.includes('Assertion passed:')) return 'text-green-400';
140
+ if (message.includes('Assertion failed:')) return 'text-red-400';
141
+ if (message.includes('RCON command:')) return 'text-yellow-300';
142
+ if (message.includes('Found ') || message.includes('Retrieved ')) return 'text-blue-300';
143
+ if (message.includes('Stored result as')) return 'text-purple-300';
144
+ if (message.includes('Waited ')) return 'text-gray-400';
145
+ return 'text-gray-300';
146
+ }
147
+ }
148
+ }).mount('#app');
149
+ </script>
150
+ </body>
151
+ </html>
152
+
@@ -0,0 +1,9 @@
1
+ /* Minimal styles - Tailwind handles most */
2
+ body {
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4
+ }
5
+
6
+ /* Minecraft PE font for h1 */
7
+ h1 {
8
+ font-family: 'MINECRAFT PE', sans-serif !important;
9
+ }