@fdm-monster/client-next 2.2.2 → 2.2.3

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.
Files changed (38) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/README.md +19 -0
  3. package/RELEASE_NOTES.MD +10 -0
  4. package/dist/assets/{index-BlOaSQti.js → index-BAB7cJ3l.js} +52 -52
  5. package/dist/assets/{index-BlOaSQti.js.map → index-BAB7cJ3l.js.map} +1 -1
  6. package/dist/assets/index-DfA7W6iO.css +1 -0
  7. package/dist/index.html +3 -3
  8. package/package.json +21 -2
  9. package/screenshots/COVERAGE.md +383 -0
  10. package/screenshots/README.md +431 -0
  11. package/screenshots/fixtures/api-mock.ts +699 -0
  12. package/screenshots/fixtures/data/auth.fixtures.ts +79 -0
  13. package/screenshots/fixtures/data/cameras.fixtures.ts +48 -0
  14. package/screenshots/fixtures/data/files.fixtures.ts +56 -0
  15. package/screenshots/fixtures/data/floors.fixtures.ts +39 -0
  16. package/screenshots/fixtures/data/jobs.fixtures.ts +172 -0
  17. package/screenshots/fixtures/data/printers.fixtures.ts +132 -0
  18. package/screenshots/fixtures/data/settings.fixtures.ts +62 -0
  19. package/screenshots/fixtures/socketio-mock.ts +76 -0
  20. package/screenshots/fixtures/test-fixtures.ts +112 -0
  21. package/screenshots/helpers/dialog.helper.ts +196 -0
  22. package/screenshots/helpers/form.helper.ts +207 -0
  23. package/screenshots/helpers/navigation.helper.ts +191 -0
  24. package/screenshots/playwright.screenshots.config.ts +70 -0
  25. package/screenshots/suites/00-example.screenshots.spec.ts +29 -0
  26. package/screenshots/suites/01-auth.screenshots.spec.ts +130 -0
  27. package/screenshots/suites/02-dashboard.screenshots.spec.ts +106 -0
  28. package/screenshots/suites/03-printer-grid.screenshots.spec.ts +160 -0
  29. package/screenshots/suites/04-printer-list.screenshots.spec.ts +184 -0
  30. package/screenshots/suites/05-camera-grid.screenshots.spec.ts +127 -0
  31. package/screenshots/suites/06-print-jobs.screenshots.spec.ts +139 -0
  32. package/screenshots/suites/07-queue.screenshots.spec.ts +86 -0
  33. package/screenshots/suites/08-files.screenshots.spec.ts +142 -0
  34. package/screenshots/suites/09-settings.screenshots.spec.ts +130 -0
  35. package/screenshots/suites/10-panels-dialogs.screenshots.spec.ts +245 -0
  36. package/screenshots/utils.ts +216 -0
  37. package/vitest.config.ts +8 -0
  38. package/dist/assets/index-TeWdSn_6.css +0 -1
@@ -0,0 +1,245 @@
1
+ import { test, expect } from '../fixtures/test-fixtures';
2
+ import { captureFullPage } from '../utils';
3
+ import { createNavigationHelper } from '../helpers/navigation.helper';
4
+ import { createDialogHelper } from '../helpers/dialog.helper';
5
+
6
+ /**
7
+ * Panels and Dialogs Screenshots
8
+ * Captures: YAML import/export, OctoFarm import, menus, various dialogs
9
+ */
10
+ test.describe('Panels and Dialogs Screenshots', () => {
11
+ test.beforeEach(async ({ apiMock }) => {
12
+ await apiMock.mockAllEndpoints({ loginRequired: false });
13
+ });
14
+
15
+ test('01-yaml-export-panel', async ({ authenticatedPage }) => {
16
+ const nav = createNavigationHelper(authenticatedPage);
17
+ await nav.goToSettings();
18
+
19
+ await authenticatedPage.waitForTimeout(1000);
20
+
21
+ // Look for YAML export button
22
+ const exportBtn = authenticatedPage.locator('button').filter({ hasText: /yaml.*export|export.*yaml/i }).first();
23
+ if (await exportBtn.isVisible().catch(() => false)) {
24
+ await exportBtn.click();
25
+ await authenticatedPage.waitForTimeout(1000);
26
+ }
27
+
28
+ await captureFullPage(authenticatedPage, 'yaml-export-panel.png', 'dialogs');
29
+ });
30
+
31
+ test('02-yaml-import-panel', async ({ authenticatedPage }) => {
32
+ const nav = createNavigationHelper(authenticatedPage);
33
+ await nav.goToSettings();
34
+
35
+ await authenticatedPage.waitForTimeout(1000);
36
+
37
+ // Look for YAML import button
38
+ const importBtn = authenticatedPage.locator('button').filter({ hasText: /yaml.*import|import.*yaml/i }).first();
39
+ if (await importBtn.isVisible().catch(() => false)) {
40
+ await importBtn.click();
41
+ await authenticatedPage.waitForTimeout(1000);
42
+ }
43
+
44
+ await captureFullPage(authenticatedPage, 'yaml-import-panel.png', 'dialogs');
45
+ });
46
+
47
+ test('03-octofarm-import-panel', async ({ authenticatedPage }) => {
48
+ const nav = createNavigationHelper(authenticatedPage);
49
+ await nav.goToSettings();
50
+
51
+ await authenticatedPage.waitForTimeout(1000);
52
+
53
+ // Look for OctoFarm import button
54
+ const importBtn = authenticatedPage.locator('button').filter({ hasText: /octofarm.*import|import.*octofarm/i }).first();
55
+ if (await importBtn.isVisible().catch(() => false)) {
56
+ await importBtn.click();
57
+ await authenticatedPage.waitForTimeout(1000);
58
+ }
59
+
60
+ await captureFullPage(authenticatedPage, 'octofarm-import-panel.png', 'dialogs');
61
+ });
62
+
63
+ test('04-jobs-menu', async ({ authenticatedPage }) => {
64
+ const nav = createNavigationHelper(authenticatedPage);
65
+ await nav.goToJobs();
66
+
67
+ await authenticatedPage.waitForTimeout(1000);
68
+
69
+ // Open jobs menu
70
+ const menuBtn = authenticatedPage.locator('button[aria-label*="menu" i], .menu-btn').first();
71
+ if (await menuBtn.isVisible().catch(() => false)) {
72
+ await menuBtn.click();
73
+ await authenticatedPage.waitForTimeout(500);
74
+ }
75
+
76
+ await captureFullPage(authenticatedPage, 'jobs-menu.png', 'dialogs');
77
+ });
78
+
79
+ test('05-printer-status-menu', async ({ authenticatedPage }) => {
80
+ const nav = createNavigationHelper(authenticatedPage);
81
+ await nav.goToPrinterGrid();
82
+
83
+ await authenticatedPage.waitForTimeout(1000);
84
+
85
+ // Click on a printer to open status menu
86
+ const printerCard = authenticatedPage.locator('.printer-card, [data-testid="printer-card"]').first();
87
+ if (await printerCard.isVisible().catch(() => false)) {
88
+ // Look for menu button on printer card
89
+ const menuBtn = printerCard.locator('button[aria-label*="menu" i], .menu-btn').first();
90
+ if (await menuBtn.isVisible().catch(() => false)) {
91
+ await menuBtn.click();
92
+ await authenticatedPage.waitForTimeout(500);
93
+ }
94
+ }
95
+
96
+ await captureFullPage(authenticatedPage, 'printer-status-menu.png', 'dialogs');
97
+ });
98
+
99
+ test('06-printer-dialog-create', async ({ authenticatedPage }) => {
100
+ const nav = createNavigationHelper(authenticatedPage);
101
+ await nav.goToPrinters();
102
+
103
+ await authenticatedPage.waitForTimeout(1000);
104
+
105
+ const addBtn = authenticatedPage.locator('button').filter({ hasText: /add|create|new/i }).first();
106
+ await addBtn.click().catch(() => {});
107
+
108
+ const dialog = createDialogHelper(authenticatedPage);
109
+ await dialog.waitForDialog();
110
+
111
+ await authenticatedPage.waitForTimeout(500);
112
+
113
+ await captureFullPage(authenticatedPage, 'printer-dialog-create.png', 'dialogs');
114
+ });
115
+
116
+ test('07-printer-dialog-update', async ({ authenticatedPage }) => {
117
+ const nav = createNavigationHelper(authenticatedPage);
118
+ await nav.goToPrinters();
119
+
120
+ await authenticatedPage.waitForTimeout(1000);
121
+
122
+ // Click edit on first printer
123
+ const editBtn = authenticatedPage.locator('button').filter({ hasText: /edit/i }).first();
124
+ if (await editBtn.isVisible().catch(() => false)) {
125
+ await editBtn.click();
126
+ await authenticatedPage.waitForTimeout(1000);
127
+ }
128
+
129
+ await captureFullPage(authenticatedPage, 'printer-dialog-update.png', 'dialogs');
130
+ });
131
+
132
+ test('08-printer-dialog-duplicate', async ({ authenticatedPage }) => {
133
+ const nav = createNavigationHelper(authenticatedPage);
134
+ await nav.goToPrinters();
135
+
136
+ await authenticatedPage.waitForTimeout(1000);
137
+
138
+ // Look for duplicate button
139
+ const duplicateBtn = authenticatedPage.locator('button').filter({ hasText: /duplicate/i }).first();
140
+ if (await duplicateBtn.isVisible().catch(() => false)) {
141
+ await duplicateBtn.click();
142
+ await authenticatedPage.waitForTimeout(1000);
143
+ }
144
+
145
+ await captureFullPage(authenticatedPage, 'printer-dialog-duplicate.png', 'dialogs');
146
+ });
147
+
148
+ test('09-printer-test-connection-dialog', async ({ authenticatedPage }) => {
149
+ const nav = createNavigationHelper(authenticatedPage);
150
+ await nav.goToPrinters();
151
+
152
+ await authenticatedPage.waitForTimeout(1000);
153
+
154
+ // Look for test connection button
155
+ const testBtn = authenticatedPage.locator('button').filter({ hasText: /test.*connection|connection.*test/i }).first();
156
+ if (await testBtn.isVisible().catch(() => false)) {
157
+ await testBtn.click();
158
+ await authenticatedPage.waitForTimeout(1500);
159
+ }
160
+
161
+ await captureFullPage(authenticatedPage, 'printer-test-connection-dialog.png', 'dialogs');
162
+ });
163
+
164
+ test('10-printer-type-dropdown', async ({ authenticatedPage }) => {
165
+ const nav = createNavigationHelper(authenticatedPage);
166
+ await nav.goToPrinters();
167
+
168
+ await authenticatedPage.waitForTimeout(1000);
169
+
170
+ // Open create printer dialog
171
+ const addBtn = authenticatedPage.locator('button').filter({ hasText: /add|create/i }).first();
172
+ await addBtn.click().catch(() => {});
173
+
174
+ const dialog = createDialogHelper(authenticatedPage);
175
+ await dialog.waitForDialog();
176
+
177
+ await authenticatedPage.waitForTimeout(500);
178
+
179
+ // Click on printer type dropdown
180
+ const typeSelect = authenticatedPage.locator('.v-dialog select, .v-dialog .v-select').filter({ hasText: /type/i }).first();
181
+ if (await typeSelect.isVisible().catch(() => false)) {
182
+ await typeSelect.click();
183
+ await authenticatedPage.waitForTimeout(500);
184
+ }
185
+
186
+ await captureFullPage(authenticatedPage, 'printer-type-dropdown.png', 'dialogs');
187
+ });
188
+
189
+ test('11-printer-force-save-warning', async ({ authenticatedPage }) => {
190
+ const nav = createNavigationHelper(authenticatedPage);
191
+ await nav.goToPrinters();
192
+
193
+ await authenticatedPage.waitForTimeout(1000);
194
+
195
+ // Open create printer dialog and try to save without required fields
196
+ const addBtn = authenticatedPage.locator('button').filter({ hasText: /add|create/i }).first();
197
+ await addBtn.click().catch(() => {});
198
+
199
+ const dialog = createDialogHelper(authenticatedPage);
200
+ await dialog.waitForDialog();
201
+
202
+ await authenticatedPage.waitForTimeout(500);
203
+
204
+ // Try to save (should show validation or force save option)
205
+ const saveBtn = authenticatedPage.locator('.v-dialog button').filter({ hasText: /save|create/i }).first();
206
+ if (await saveBtn.isVisible().catch(() => false)) {
207
+ await saveBtn.click();
208
+ await authenticatedPage.waitForTimeout(1000);
209
+ }
210
+
211
+ await captureFullPage(authenticatedPage, 'printer-force-save-warning.png', 'dialogs');
212
+ });
213
+
214
+ test('12-edit-floor-panel', async ({ authenticatedPage }) => {
215
+ const nav = createNavigationHelper(authenticatedPage);
216
+ await nav.goToSettings('floors');
217
+
218
+ await authenticatedPage.waitForTimeout(1000);
219
+
220
+ // Click on first floor to edit
221
+ const floorItem = authenticatedPage.locator('.floor-item, [data-testid="floor-item"], tr').first();
222
+ if (await floorItem.isVisible().catch(() => false)) {
223
+ await floorItem.click();
224
+ await authenticatedPage.waitForTimeout(1000);
225
+ }
226
+
227
+ await captureFullPage(authenticatedPage, 'edit-floor-panel.png', 'dialogs');
228
+ });
229
+
230
+ test('13-delete-confirmation-dialog', async ({ authenticatedPage }) => {
231
+ const nav = createNavigationHelper(authenticatedPage);
232
+ await nav.goToPrinters();
233
+
234
+ await authenticatedPage.waitForTimeout(1000);
235
+
236
+ // Look for delete button
237
+ const deleteBtn = authenticatedPage.locator('button').filter({ hasText: /delete|remove/i }).first();
238
+ if (await deleteBtn.isVisible().catch(() => false)) {
239
+ await deleteBtn.click();
240
+ await authenticatedPage.waitForTimeout(500);
241
+ }
242
+
243
+ await captureFullPage(authenticatedPage, 'delete-confirmation-dialog.png', 'dialogs');
244
+ });
245
+ });
@@ -0,0 +1,216 @@
1
+ import { Page } from '@playwright/test';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+
5
+ /**
6
+ * Utility functions for taking documentation screenshots
7
+ */
8
+
9
+ /**
10
+ * Ensure a directory exists, create it if not
11
+ */
12
+ export function ensureDir(dirPath: string): void {
13
+ if (!fs.existsSync(dirPath)) {
14
+ fs.mkdirSync(dirPath, { recursive: true });
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Get screenshot output path
20
+ */
21
+ export function getScreenshotPath(filename: string, subdir?: string): string {
22
+ const baseDir = path.join(__dirname, 'output');
23
+ const targetDir = subdir ? path.join(baseDir, subdir) : baseDir;
24
+ ensureDir(targetDir);
25
+ return path.join(targetDir, filename);
26
+ }
27
+
28
+ /**
29
+ * Wait for page to be fully loaded and animations to complete
30
+ */
31
+ export async function waitForPageReady(page: Page, timeout = 1000): Promise<void> {
32
+ await page.waitForLoadState('networkidle');
33
+ await page.waitForTimeout(timeout); // Allow animations to complete
34
+ }
35
+
36
+ /**
37
+ * Capture a full page screenshot with consistent settings
38
+ */
39
+ export async function captureFullPage(
40
+ page: Page,
41
+ filename: string,
42
+ subdir?: string
43
+ ): Promise<void> {
44
+ await waitForPageReady(page);
45
+ await page.screenshot({
46
+ path: getScreenshotPath(filename, subdir),
47
+ fullPage: true,
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Capture viewport screenshot with consistent settings
53
+ */
54
+ export async function captureViewport(
55
+ page: Page,
56
+ filename: string,
57
+ subdir?: string
58
+ ): Promise<void> {
59
+ await waitForPageReady(page);
60
+ await page.screenshot({
61
+ path: getScreenshotPath(filename, subdir),
62
+ fullPage: false,
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Capture element screenshot
68
+ */
69
+ export async function captureElement(
70
+ page: Page,
71
+ selector: string,
72
+ filename: string,
73
+ subdir?: string
74
+ ): Promise<void> {
75
+ await waitForPageReady(page);
76
+ const element = page.locator(selector).first();
77
+
78
+ if (await element.count() === 0) {
79
+ console.warn(`Element not found: ${selector}`);
80
+ return;
81
+ }
82
+
83
+ await element.screenshot({
84
+ path: getScreenshotPath(filename, subdir),
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Set up authentication (example - adjust based on your auth implementation)
90
+ */
91
+ export async function setupAuth(page: Page, token?: string): Promise<void> {
92
+ if (token) {
93
+ await page.goto('/');
94
+ await page.evaluate((authToken) => {
95
+ localStorage.setItem('auth-token', authToken);
96
+ }, token);
97
+ } else {
98
+ // Default login flow
99
+ await page.goto('/login');
100
+ // Add your login logic here
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Capture screenshots at multiple viewport sizes
106
+ */
107
+ export async function captureResponsive(
108
+ page: Page,
109
+ route: string,
110
+ baseFilename: string,
111
+ viewports: Array<{ width: number; height: number; name: string }>,
112
+ subdir?: string
113
+ ): Promise<void> {
114
+ for (const viewport of viewports) {
115
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
116
+ await page.goto(route);
117
+ await waitForPageReady(page);
118
+
119
+ const filename = `${baseFilename}-${viewport.name}.png`;
120
+ await page.screenshot({
121
+ path: getScreenshotPath(filename, subdir),
122
+ fullPage: false,
123
+ });
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Common viewport presets
129
+ */
130
+ export const VIEWPORTS = {
131
+ DESKTOP_FHD: { width: 1920, height: 1080, name: 'desktop-1920x1080' },
132
+ DESKTOP_HD: { width: 1366, height: 768, name: 'desktop-1366x768' },
133
+ DESKTOP_LAPTOP: { width: 1280, height: 720, name: 'laptop-1280x720' },
134
+ TABLET_PORTRAIT: { width: 768, height: 1024, name: 'tablet-portrait' },
135
+ TABLET_LANDSCAPE: { width: 1024, height: 768, name: 'tablet-landscape' },
136
+ MOBILE_IPHONE_SE: { width: 375, height: 667, name: 'mobile-iphone-se' },
137
+ MOBILE_IPHONE_12: { width: 390, height: 844, name: 'mobile-iphone-12' },
138
+ MOBILE_PIXEL_5: { width: 393, height: 851, name: 'mobile-pixel-5' },
139
+ };
140
+
141
+ /**
142
+ * Capture both light and dark theme screenshots
143
+ */
144
+ export async function captureBothThemes(
145
+ page: Page,
146
+ route: string,
147
+ baseFilename: string,
148
+ themeToggleSelector: string,
149
+ subdir?: string
150
+ ): Promise<void> {
151
+ await page.goto(route);
152
+ await waitForPageReady(page);
153
+
154
+ // Capture light theme
155
+ await page.screenshot({
156
+ path: getScreenshotPath(`${baseFilename}-light.png`, subdir),
157
+ fullPage: false,
158
+ });
159
+
160
+ // Toggle to dark theme
161
+ const themeToggle = page.locator(themeToggleSelector);
162
+ if (await themeToggle.count() > 0) {
163
+ await themeToggle.click();
164
+ await page.waitForTimeout(500); // Wait for theme transition
165
+
166
+ // Capture dark theme
167
+ await page.screenshot({
168
+ path: getScreenshotPath(`${baseFilename}-dark.png`, subdir),
169
+ fullPage: false,
170
+ });
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Hide elements before taking screenshot (useful for hiding dynamic content)
176
+ */
177
+ export async function hideElements(page: Page, selectors: string[]): Promise<void> {
178
+ for (const selector of selectors) {
179
+ await page.locator(selector).evaluateAll((elements) => {
180
+ elements.forEach((el: HTMLElement) => {
181
+ el.style.visibility = 'hidden';
182
+ });
183
+ });
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Mask sensitive data in screenshots
189
+ */
190
+ export async function maskElements(page: Page, selectors: string[]): Promise<void> {
191
+ for (const selector of selectors) {
192
+ await page.locator(selector).evaluateAll((elements) => {
193
+ elements.forEach((el: HTMLElement) => {
194
+ el.style.filter = 'blur(10px)';
195
+ });
196
+ });
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Wait for specific element to be visible
202
+ */
203
+ export async function waitForElement(
204
+ page: Page,
205
+ selector: string,
206
+ timeout = 5000
207
+ ): Promise<boolean> {
208
+ try {
209
+ await page.waitForSelector(selector, { timeout, state: 'visible' });
210
+ return true;
211
+ } catch {
212
+ console.warn(`Element not visible within timeout: ${selector}`);
213
+ return false;
214
+ }
215
+ }
216
+
package/vitest.config.ts CHANGED
@@ -6,6 +6,14 @@ export default mergeConfig(viteConfig, defineConfig({
6
6
  globals: true,
7
7
  setupFiles: ['./test/setup-axios-mock.ts'],
8
8
  environment: 'jsdom',
9
+ exclude: [
10
+ '**/node_modules/**',
11
+ '**/dist/**',
12
+ '**/cypress/**',
13
+ '**/.{idea,git,cache,output,temp}/**',
14
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
15
+ '**/screenshots/**', // Exclude Playwright screenshot tests
16
+ ],
9
17
  server: {
10
18
  deps: {
11
19
  inline: ['vuetify'],