puglite 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,260 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Puglite Karma Test Builder",
4
+ "description": "Karma test runner with Puglite template support",
5
+ "type": "object",
6
+ "properties": {
7
+ "main": {
8
+ "type": "string",
9
+ "description": "The name of the main entry-point file."
10
+ },
11
+ "tsConfig": {
12
+ "type": "string",
13
+ "description": "The name of the TypeScript configuration file."
14
+ },
15
+ "karmaConfig": {
16
+ "type": "string",
17
+ "description": "The name of the Karma configuration file."
18
+ },
19
+ "polyfills": {
20
+ "description": "A list of polyfills to include in the build.",
21
+ "oneOf": [
22
+ {
23
+ "type": "array",
24
+ "items": {
25
+ "type": "string"
26
+ }
27
+ },
28
+ {
29
+ "type": "string"
30
+ }
31
+ ]
32
+ },
33
+ "assets": {
34
+ "type": "array",
35
+ "description": "List of static application assets.",
36
+ "default": [],
37
+ "items": {
38
+ "oneOf": [
39
+ {
40
+ "type": "string"
41
+ },
42
+ {
43
+ "type": "object",
44
+ "properties": {
45
+ "glob": {
46
+ "type": "string",
47
+ "description": "The pattern to match."
48
+ },
49
+ "input": {
50
+ "type": "string",
51
+ "description": "The input directory path in which to apply 'glob'."
52
+ },
53
+ "output": {
54
+ "type": "string",
55
+ "description": "Absolute path within the output."
56
+ },
57
+ "ignore": {
58
+ "description": "An array of globs to ignore.",
59
+ "type": "array",
60
+ "items": {
61
+ "type": "string"
62
+ }
63
+ }
64
+ },
65
+ "additionalProperties": false,
66
+ "required": ["glob", "input", "output"]
67
+ }
68
+ ]
69
+ }
70
+ },
71
+ "styles": {
72
+ "description": "Global styles to be included in the build.",
73
+ "type": "array",
74
+ "default": [],
75
+ "items": {
76
+ "oneOf": [
77
+ {
78
+ "type": "string"
79
+ },
80
+ {
81
+ "type": "object",
82
+ "properties": {
83
+ "input": {
84
+ "type": "string",
85
+ "description": "The file to include."
86
+ },
87
+ "bundleName": {
88
+ "type": "string",
89
+ "pattern": "^[\\w\\-.]*$",
90
+ "description": "The bundle name for this extra entry point."
91
+ },
92
+ "inject": {
93
+ "type": "boolean",
94
+ "description": "If the bundle will be referenced in the HTML file.",
95
+ "default": true
96
+ }
97
+ },
98
+ "additionalProperties": false,
99
+ "required": ["input"]
100
+ }
101
+ ]
102
+ }
103
+ },
104
+ "scripts": {
105
+ "description": "Global scripts to be included in the build.",
106
+ "type": "array",
107
+ "default": [],
108
+ "items": {
109
+ "oneOf": [
110
+ {
111
+ "type": "string"
112
+ },
113
+ {
114
+ "type": "object",
115
+ "properties": {
116
+ "input": {
117
+ "type": "string",
118
+ "description": "The file to include."
119
+ },
120
+ "bundleName": {
121
+ "type": "string",
122
+ "pattern": "^[\\w\\-.]*$",
123
+ "description": "The bundle name for this extra entry point."
124
+ },
125
+ "inject": {
126
+ "type": "boolean",
127
+ "description": "If the bundle will be referenced in the HTML file.",
128
+ "default": true
129
+ }
130
+ },
131
+ "additionalProperties": false,
132
+ "required": ["input"]
133
+ }
134
+ ]
135
+ }
136
+ },
137
+ "sourceMap": {
138
+ "description": "Output source maps for scripts and styles.",
139
+ "default": true,
140
+ "oneOf": [
141
+ {
142
+ "type": "boolean"
143
+ },
144
+ {
145
+ "type": "object",
146
+ "properties": {
147
+ "scripts": {
148
+ "type": "boolean",
149
+ "description": "Output source maps for all scripts.",
150
+ "default": true
151
+ },
152
+ "styles": {
153
+ "type": "boolean",
154
+ "description": "Output source maps for all styles.",
155
+ "default": true
156
+ },
157
+ "vendor": {
158
+ "type": "boolean",
159
+ "description": "Output source maps for vendor libraries.",
160
+ "default": false
161
+ }
162
+ },
163
+ "additionalProperties": false
164
+ }
165
+ ]
166
+ },
167
+ "progress": {
168
+ "type": "boolean",
169
+ "description": "Log progress to the console while building."
170
+ },
171
+ "watch": {
172
+ "type": "boolean",
173
+ "description": "Run build when files change."
174
+ },
175
+ "poll": {
176
+ "type": "number",
177
+ "description": "Enable and define the file watching poll time period in milliseconds."
178
+ },
179
+ "preserveSymlinks": {
180
+ "type": "boolean",
181
+ "description": "Do not use the real path when resolving modules."
182
+ },
183
+ "browsers": {
184
+ "type": "string",
185
+ "description": "Override which browsers tests are run against."
186
+ },
187
+ "codeCoverage": {
188
+ "type": "boolean",
189
+ "description": "Output a code coverage report.",
190
+ "default": false
191
+ },
192
+ "codeCoverageExclude": {
193
+ "type": "array",
194
+ "description": "Globs to exclude from code coverage.",
195
+ "items": {
196
+ "type": "string"
197
+ },
198
+ "default": []
199
+ },
200
+ "fileReplacements": {
201
+ "description": "Replace compilation source files with other compilation source files in the build.",
202
+ "type": "array",
203
+ "items": {
204
+ "oneOf": [
205
+ {
206
+ "type": "object",
207
+ "properties": {
208
+ "src": {
209
+ "type": "string",
210
+ "pattern": "\\.(([cm]?j|t)sx?|json)$"
211
+ },
212
+ "replaceWith": {
213
+ "type": "string",
214
+ "pattern": "\\.(([cm]?j|t)sx?|json)$"
215
+ }
216
+ },
217
+ "additionalProperties": false,
218
+ "required": ["src", "replaceWith"]
219
+ },
220
+ {
221
+ "type": "object",
222
+ "properties": {
223
+ "replace": {
224
+ "type": "string",
225
+ "pattern": "\\.(([cm]?j|t)sx?|json)$"
226
+ },
227
+ "with": {
228
+ "type": "string",
229
+ "pattern": "\\.(([cm]?j|t)sx?|json)$"
230
+ }
231
+ },
232
+ "additionalProperties": false,
233
+ "required": ["replace", "with"]
234
+ }
235
+ ]
236
+ },
237
+ "default": []
238
+ },
239
+ "include": {
240
+ "type": "array",
241
+ "description": "Globs of files to include, relative to workspace or project root.",
242
+ "items": {
243
+ "type": "string"
244
+ }
245
+ },
246
+ "exclude": {
247
+ "type": "array",
248
+ "description": "Globs of files to exclude, relative to the workspace or project root.",
249
+ "items": {
250
+ "type": "string"
251
+ }
252
+ },
253
+ "webWorkerTsConfig": {
254
+ "type": "string",
255
+ "description": "TypeScript configuration for Web Worker modules."
256
+ }
257
+ },
258
+ "additionalProperties": false,
259
+ "required": ["karmaConfig"]
260
+ }
@@ -0,0 +1,218 @@
1
+ # Puglite Vitest Builder
2
+
3
+ Angular builder that runs Vitest with Pug template support.
4
+
5
+ ## Usage
6
+
7
+ In `angular.json`:
8
+
9
+ ```json
10
+ {
11
+ "architect": {
12
+ "test": {
13
+ "builder": "puglite:vitest",
14
+ "options": {}
15
+ }
16
+ }
17
+ }
18
+ ```
19
+
20
+ Run tests:
21
+
22
+ ```bash
23
+ ng test # Run all tests via builder
24
+ npx vitest <file> # Run specific test file directly
25
+ ```
26
+
27
+ ## How It Works
28
+
29
+ The builder:
30
+ 1. Calls Vitest's `startVitest()` API directly
31
+ 2. Injects a Vite plugin to transform `.pug` files to HTML strings
32
+ 3. Enables `jsdom` environment for browser API support (localStorage, window, etc.)
33
+
34
+ ## Important: You Probably Don't Need Pug Plugin for Tests
35
+
36
+ **Best practice:** Test component logic only, without importing templates.
37
+
38
+ ```typescript
39
+ // ✅ Test component logic only - NO template imports needed
40
+ describe('UploadImgComponent', () => {
41
+ let component: UploadImgComponent;
42
+
43
+ beforeEach(() => {
44
+ const mockService = { upload: vi.fn() };
45
+ component = new UploadImgComponent(mockService); // No TestBed
46
+ });
47
+
48
+ it('should update state', () => {
49
+ component.uploadState.set({ status: 'uploading', progress: 50 });
50
+ expect(component.isUploading()).toBe(true);
51
+ });
52
+ });
53
+ ```
54
+
55
+ **Template/UI testing happens in E2E:**
56
+
57
+ ```typescript
58
+ // e2e/upload.spec.ts
59
+ test('should display upload progress', async ({ page }) => {
60
+ await page.goto('/upload');
61
+ await expect(page.locator('.progress')).toHaveText('50%');
62
+ });
63
+ ```
64
+
65
+ ## When You DO Need Pug Plugin
66
+
67
+ Only if you want to import pug templates in tests (NOT recommended):
68
+
69
+ ```typescript
70
+ import template from './component.pug'; // ← Requires pug plugin
71
+
72
+ @Component({
73
+ selector: 'app-test',
74
+ template: template, // Inline template from import
75
+ })
76
+ class TestComponent extends RealComponent {}
77
+
78
+ TestBed.configureTestingModule({ imports: [TestComponent] })
79
+ ```
80
+
81
+ **Why NOT recommended:**
82
+ - Still requires TestBed (complex setup)
83
+ - Adds unnecessary complexity
84
+ - Better separation: logic in Vitest, UI in E2E
85
+
86
+ ## Pug Template Support (Advanced)
87
+
88
+ If you still want to import pug files in tests, the builder's plugin supports it.
89
+
90
+ **✅ Works:** Import pug files as ES modules
91
+
92
+ ```typescript
93
+ import template from './component.pug';
94
+
95
+ @Component({
96
+ selector: 'app-test',
97
+ template: template, // Inline template from import
98
+ })
99
+ class TestComponent extends RealComponent {}
100
+ ```
101
+
102
+ **❌ Doesn't work:** TestBed with external templateUrl
103
+
104
+ ```typescript
105
+ // This will FAIL in Vitest
106
+ @Component({
107
+ templateUrl: './component.pug' // External template
108
+ })
109
+ class RealComponent {}
110
+
111
+ TestBed.configureTestingModule({ imports: [RealComponent] })
112
+ .compileComponents(); // Error: Component not resolved
113
+ ```
114
+
115
+ See `angular-puglite-demo/src/app/app-templateurl.spec.ts` for a concrete example.
116
+
117
+ ## TestBed Limitation
118
+
119
+ **TestBed does NOT work with external templates (`.pug` or `.html`) in Vitest.**
120
+
121
+ ### Why?
122
+
123
+ - **TestBed's `compileComponents()`** expects to **fetch** external templates like a browser (HTTP requests)
124
+ - **Vitest** runs in Node.js with **no dev server** (unlike Karma which runs in real browser)
125
+ - **Vite plugins** transform ES module **imports** (`import x from 'file.pug'`), but TestBed uses file path strings (`templateUrl: 'file.pug'`)
126
+ - These are two different systems that never connect
127
+
128
+ ### Recommended Patterns
129
+
130
+ **Don't use TestBed in Vitest.** Follow these patterns:
131
+
132
+ #### 1. Service Tests (✅ Recommended)
133
+
134
+ ```typescript
135
+ describe('MyService', () => {
136
+ let service: MyService;
137
+
138
+ beforeEach(() => {
139
+ const httpMock = { get: vi.fn(), post: vi.fn() };
140
+ service = new MyService(httpMock); // Manual instantiation
141
+ });
142
+
143
+ it('should fetch data', () => {
144
+ // Test service logic
145
+ });
146
+ });
147
+ ```
148
+
149
+ #### 2. Component Logic Tests (✅ Recommended)
150
+
151
+ ```typescript
152
+ describe('MyComponent', () => {
153
+ let component: MyComponent;
154
+
155
+ beforeEach(() => {
156
+ const mockService = { getData: vi.fn() };
157
+ component = new MyComponent(mockService); // No TestBed
158
+ });
159
+
160
+ it('should update state', () => {
161
+ // Test component methods, signals, computed values
162
+ expect(component.items()).toEqual([]);
163
+ });
164
+ });
165
+ ```
166
+
167
+ #### 3. Template/UI Tests (✅ Use E2E)
168
+
169
+ ```typescript
170
+ // e2e/my-component.spec.ts (Playwright)
171
+ test('should display items', async ({ page }) => {
172
+ await page.goto('/my-component');
173
+ await expect(page.locator('.item')).toBeVisible();
174
+ });
175
+ ```
176
+
177
+ ## Alternative: Use Vitest Directly
178
+
179
+ Instead of using this builder, you can run Vitest directly:
180
+
181
+ **package.json:**
182
+ ```json
183
+ {
184
+ "scripts": {
185
+ "test": "vitest run",
186
+ "test:watch": "vitest",
187
+ "test:ui": "vitest --ui"
188
+ }
189
+ }
190
+ ```
191
+
192
+ **vitest.config.ts:**
193
+ ```typescript
194
+ import { defineConfig } from 'vitest/config';
195
+
196
+ export default defineConfig({
197
+ test: {
198
+ environment: 'jsdom',
199
+ globals: true,
200
+ setupFiles: ['src/test-setup.ts']
201
+ }
202
+ });
203
+ ```
204
+
205
+ **No pug plugin needed** - just test component logic with `new Component()`.
206
+
207
+ ## Summary
208
+
209
+ | What | Vitest (logic) | E2E (Playwright) |
210
+ |------|----------------|------------------|
211
+ | Service logic | ✅ `new Service()` | ❌ |
212
+ | Component logic | ✅ `new Component()` | ❌ |
213
+ | Template rendering | ❌ | ✅ |
214
+ | User interactions | ❌ | ✅ |
215
+ | DOM assertions | ❌ | ✅ |
216
+ | Pug plugin needed | ❌ No | N/A |
217
+
218
+ **Recommended: Use Vitest for logic, E2E for UI. No pug plugin needed.**
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Puglite Vitest Builder
3
+ *
4
+ * Angular builder that runs Vitest with Pug template transformation.
5
+ *
6
+ * Features:
7
+ * - Transforms .pug files to HTML strings via Vite plugin
8
+ * - Enables jsdom environment for browser API support
9
+ * - Supports file filters for running specific tests
10
+ *
11
+ * Limitation:
12
+ * - TestBed with external templates does NOT work in Vitest
13
+ * - See README.md for recommended testing patterns
14
+ */
15
+
16
+ const path = require('path');
17
+ const { createRequire } = require('module');
18
+ const { readFileSync } = require('fs');
19
+ const debug = require('util').debuglog('puglite');
20
+
21
+ const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
22
+
23
+ async function* buildVitest(options, context) {
24
+ debug('buildVitest() called');
25
+ const { startVitest } = projectRequire('vitest/node');
26
+
27
+ // Get file filters from CLI args
28
+ const cliArgs = process.argv.slice(2).filter(arg => !arg.startsWith('--') && arg.endsWith('.spec.ts'));
29
+ const filters = options.include || cliArgs;
30
+
31
+ // Load puglite compiler modules
32
+ const libPath = path.resolve(__dirname, '../../lib');
33
+ const lex = require(`${libPath}/lexer`);
34
+ const parse = require(`${libPath}/parser`);
35
+ const generateCode = require(`${libPath}/code-gen`);
36
+ const stripComments = require(`${libPath}/strip-comments`);
37
+ const runtimeWrap = require(`${libPath}/runtime-wrap`);
38
+
39
+ // Vite plugin: Transform .pug imports to HTML strings
40
+ const pugVitePlugin = {
41
+ name: 'puglite-vitest',
42
+ enforce: 'pre',
43
+ load(id) {
44
+ const cleanId = id.split('?')[0];
45
+ if (!cleanId.endsWith('.pug')) return null;
46
+
47
+ try {
48
+ const pugSource = readFileSync(cleanId, 'utf-8');
49
+ const tokens = lex(pugSource, { filename: cleanId });
50
+ const strippedTokens = stripComments(tokens, { filename: cleanId });
51
+ const ast = parse(strippedTokens, { filename: cleanId, src: pugSource });
52
+ const js = generateCode(ast, {
53
+ compileDebug: false,
54
+ pretty: false,
55
+ inlineRuntimeFunctions: false,
56
+ templateName: 'template'
57
+ });
58
+ const templateFn = runtimeWrap(js);
59
+ const html = templateFn();
60
+
61
+ return `export default ${JSON.stringify(html)};`;
62
+ } catch (error) {
63
+ console.error(`[puglite] Error compiling ${cleanId}:`, error);
64
+ throw error;
65
+ }
66
+ }
67
+ };
68
+
69
+ const vitestConfig = {
70
+ run: true,
71
+ plugins: [pugVitePlugin],
72
+ test: {
73
+ globals: true,
74
+ environment: 'jsdom'
75
+ }
76
+ };
77
+
78
+ try {
79
+ const vitest = await startVitest('test', filters, vitestConfig);
80
+
81
+ if (!vitest) {
82
+ yield { success: false };
83
+ return;
84
+ }
85
+
86
+ await vitest.close();
87
+
88
+ const hasFailures = vitest.state.getCountOfFailedTests() > 0;
89
+ yield { success: !hasFailures };
90
+ } catch (error) {
91
+ console.error('[puglite] Vitest error:', error);
92
+ yield { success: false, error: error.message };
93
+ }
94
+ }
95
+
96
+ const { createBuilder } = projectRequire('@angular-devkit/architect');
97
+ module.exports = createBuilder(buildVitest);
98
+ module.exports.default = module.exports;
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Puglite Vitest Builder",
4
+ "description": "Vitest test runner with Puglite template support",
5
+ "type": "object",
6
+ "properties": {},
7
+ "additionalProperties": true
8
+ }
package/builders.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "./node_modules/@angular-devkit/architect/src/builders-schema.json",
3
+ "builders": {
4
+ "browser": {
5
+ "implementation": "./builders/browser",
6
+ "schema": "./builders/browser/schema.json",
7
+ "description": "Build browser application with Puglite template support"
8
+ },
9
+ "dev-server": {
10
+ "implementation": "./builders/dev-server",
11
+ "schema": "./builders/dev-server/schema.json",
12
+ "description": "Development server with Puglite template support"
13
+ },
14
+ "karma": {
15
+ "implementation": "./builders/karma",
16
+ "schema": "./builders/karma/schema.json",
17
+ "description": "Karma test runner with Puglite template support"
18
+ },
19
+ "vitest": {
20
+ "implementation": "./builders/vitest",
21
+ "schema": "./builders/vitest/schema.json",
22
+ "description": "Vitest test runner with Puglite template support"
23
+ }
24
+ }
25
+ }