Skip to content

Commit 5f43492

Browse files
committed
prettier fix, extended tests
1 parent 985728a commit 5f43492

File tree

6 files changed

+1010
-28
lines changed

6 files changed

+1010
-28
lines changed

e2e/app-config.spec.ts

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,232 @@ test.describe('Streaming Settings', () => {
206206
})
207207
})
208208

209+
test.describe('IOT Settings', () => {
210+
test.beforeEach(async ({ page }) => {
211+
await page.goto('/')
212+
await login(page)
213+
await navigateToAppSettings(page)
214+
await page.waitForSelector('text=OMDB Settings')
215+
216+
// Navigate to IOT settings
217+
const iotMenuItem = page.getByText('Device Availability')
218+
await iotMenuItem.click()
219+
await page.waitForSelector('text=📡 IOT Device Availability')
220+
})
221+
222+
test('should display IOT settings form', async ({ page }) => {
223+
// Verify IOT settings heading (using text selector to pierce shadow DOM)
224+
await expect(page.locator('text=📡 IOT Device Availability').first()).toBeVisible()
225+
226+
const iotPage = page.locator('iot-settings-page')
227+
228+
// Verify form fields are present
229+
const pingIntervalInput = iotPage.locator('input[name="pingIntervalMs"]')
230+
await expect(pingIntervalInput).toBeVisible()
231+
232+
const pingTimeoutInput = iotPage.locator('input[name="pingTimeoutMs"]')
233+
await expect(pingTimeoutInput).toBeVisible()
234+
235+
const saveButton = iotPage.getByRole('button', { name: /save settings/i })
236+
await expect(saveButton).toBeVisible()
237+
})
238+
239+
test('should validate ping interval input constraints', async ({ page }) => {
240+
const iotPage = page.locator('iot-settings-page')
241+
const pingIntervalInput = iotPage.locator('input[name="pingIntervalMs"]')
242+
243+
// Verify min/max attributes
244+
await expect(pingIntervalInput).toHaveAttribute('min', '1000')
245+
await expect(pingIntervalInput).toHaveAttribute('max', '3600000')
246+
await expect(pingIntervalInput).toHaveAttribute('type', 'number')
247+
})
248+
249+
test('should validate ping timeout input constraints', async ({ page }) => {
250+
const iotPage = page.locator('iot-settings-page')
251+
const pingTimeoutInput = iotPage.locator('input[name="pingTimeoutMs"]')
252+
253+
// Verify min/max attributes
254+
await expect(pingTimeoutInput).toHaveAttribute('min', '100')
255+
await expect(pingTimeoutInput).toHaveAttribute('max', '60000')
256+
await expect(pingTimeoutInput).toHaveAttribute('type', 'number')
257+
})
258+
259+
test('should save IOT settings successfully', async ({ page }) => {
260+
const iotPage = page.locator('iot-settings-page')
261+
const pingIntervalInput = iotPage.locator('input[name="pingIntervalMs"]')
262+
const pingTimeoutInput = iotPage.locator('input[name="pingTimeoutMs"]')
263+
264+
// Set valid values
265+
await pingIntervalInput.fill('60000')
266+
await pingTimeoutInput.fill('5000')
267+
268+
// Submit the form
269+
const saveButton = iotPage.getByRole('button', { name: /save settings/i })
270+
await saveButton.click()
271+
272+
// Verify success notification
273+
await assertAndDismissNoty(page, 'IOT settings saved successfully')
274+
})
275+
276+
test('should persist IOT settings after save', async ({ page }) => {
277+
const iotPage = page.locator('iot-settings-page')
278+
const pingIntervalInput = iotPage.locator('input[name="pingIntervalMs"]')
279+
const pingTimeoutInput = iotPage.locator('input[name="pingTimeoutMs"]')
280+
281+
// Set specific values
282+
await pingIntervalInput.fill('45000')
283+
await pingTimeoutInput.fill('4500')
284+
285+
// Submit the form
286+
const saveButton = iotPage.getByRole('button', { name: /save settings/i })
287+
await saveButton.click()
288+
await assertAndDismissNoty(page, 'IOT settings saved successfully')
289+
290+
// Navigate away and back
291+
await page.goto('/')
292+
await page.waitForSelector('text=Apps')
293+
294+
// Navigate back to IOT settings
295+
await navigateToAppSettings(page)
296+
await page.waitForSelector('text=OMDB Settings')
297+
298+
const iotMenuItemAfter = page.getByText('Device Availability')
299+
await iotMenuItemAfter.click()
300+
await page.waitForSelector('text=📡 IOT Device Availability')
301+
302+
// Verify the values are persisted
303+
const pingIntervalInputAfter = page.locator('iot-settings-page').locator('input[name="pingIntervalMs"]')
304+
const pingTimeoutInputAfter = page.locator('iot-settings-page').locator('input[name="pingTimeoutMs"]')
305+
await expect(pingIntervalInputAfter).toHaveValue('45000')
306+
await expect(pingTimeoutInputAfter).toHaveValue('4500')
307+
})
308+
309+
test('should show validation error when timeout is greater than interval', async ({ page }) => {
310+
const iotPage = page.locator('iot-settings-page')
311+
const pingIntervalInput = iotPage.locator('input[name="pingIntervalMs"]')
312+
const pingTimeoutInput = iotPage.locator('input[name="pingTimeoutMs"]')
313+
314+
// Set invalid values (timeout >= interval)
315+
await pingIntervalInput.fill('5000')
316+
await pingTimeoutInput.fill('5000')
317+
318+
// Submit the form
319+
const saveButton = iotPage.getByRole('button', { name: /save settings/i })
320+
await saveButton.click()
321+
322+
// Verify validation error is shown
323+
const validationError = iotPage.locator('[data-testid="validation-error"]')
324+
await expect(validationError).toBeVisible()
325+
await expect(validationError).toContainText('Ping timeout must be less than ping interval')
326+
})
327+
})
328+
329+
test.describe('AI Settings', () => {
330+
test.beforeEach(async ({ page }) => {
331+
await page.goto('/')
332+
await login(page)
333+
await navigateToAppSettings(page)
334+
await page.waitForSelector('text=OMDB Settings')
335+
336+
// Navigate to AI settings
337+
const aiMenuItem = page.getByText('Ollama Settings')
338+
await aiMenuItem.click()
339+
await page.waitForSelector('text=🤖 Ollama Integration')
340+
})
341+
342+
test('should display AI settings form', async ({ page }) => {
343+
// Verify AI settings heading (using text selector to pierce shadow DOM)
344+
await expect(page.locator('text=🤖 Ollama Integration').first()).toBeVisible()
345+
346+
const aiPage = page.locator('ai-settings-page')
347+
348+
// Verify form fields are present
349+
const hostInput = aiPage.locator('input[name="host"]')
350+
await expect(hostInput).toBeVisible()
351+
await expect(hostInput).toHaveAttribute('type', 'url')
352+
353+
const saveButton = aiPage.getByRole('button', { name: /save settings/i })
354+
await expect(saveButton).toBeVisible()
355+
})
356+
357+
test('should save AI settings successfully with valid URL', async ({ page }) => {
358+
const aiPage = page.locator('ai-settings-page')
359+
const hostInput = aiPage.locator('input[name="host"]')
360+
361+
// Set a valid URL
362+
await hostInput.fill('http://localhost:11434')
363+
364+
// Submit the form
365+
const saveButton = aiPage.getByRole('button', { name: /save settings/i })
366+
await saveButton.click()
367+
368+
// Verify success notification
369+
await assertAndDismissNoty(page, 'AI settings saved successfully')
370+
})
371+
372+
test('should save AI settings successfully with empty URL (disable AI)', async ({ page }) => {
373+
const aiPage = page.locator('ai-settings-page')
374+
const hostInput = aiPage.locator('input[name="host"]')
375+
376+
// Clear the URL to disable AI features
377+
await hostInput.fill('')
378+
379+
// Submit the form
380+
const saveButton = aiPage.getByRole('button', { name: /save settings/i })
381+
await saveButton.click()
382+
383+
// Verify success notification
384+
await assertAndDismissNoty(page, 'AI settings saved successfully')
385+
})
386+
387+
test('should persist AI settings after save', async ({ page }) => {
388+
const aiPage = page.locator('ai-settings-page')
389+
const hostInput = aiPage.locator('input[name="host"]')
390+
391+
// Set a specific URL
392+
const testUrl = 'http://my-ollama-server:8080'
393+
await hostInput.fill(testUrl)
394+
395+
// Submit the form
396+
const saveButton = aiPage.getByRole('button', { name: /save settings/i })
397+
await saveButton.click()
398+
await assertAndDismissNoty(page, 'AI settings saved successfully')
399+
400+
// Navigate away and back
401+
await page.goto('/')
402+
await page.waitForSelector('text=Apps')
403+
404+
// Navigate back to AI settings
405+
await navigateToAppSettings(page)
406+
await page.waitForSelector('text=OMDB Settings')
407+
408+
const aiMenuItemAfter = page.getByText('Ollama Settings')
409+
await aiMenuItemAfter.click()
410+
await page.waitForSelector('text=🤖 Ollama Integration')
411+
412+
// Verify the value is persisted
413+
const hostInputAfter = page.locator('ai-settings-page').locator('input[name="host"]')
414+
await expect(hostInputAfter).toHaveValue(testUrl)
415+
})
416+
417+
test('should show validation error for invalid URL', async ({ page }) => {
418+
const aiPage = page.locator('ai-settings-page')
419+
const hostInput = aiPage.locator('input[name="host"]')
420+
421+
// Set an invalid URL
422+
await hostInput.fill('not-a-valid-url')
423+
424+
// Submit the form
425+
const saveButton = aiPage.getByRole('button', { name: /save settings/i })
426+
await saveButton.click()
427+
428+
// Verify validation error is shown
429+
const validationError = aiPage.locator('[data-testid="validation-error"]')
430+
await expect(validationError).toBeVisible()
431+
await expect(validationError).toContainText('Please enter a valid URL')
432+
})
433+
})
434+
209435
test.describe('Settings Navigation', () => {
210436
test.beforeEach(async ({ page }) => {
211437
await page.goto('/')
@@ -234,6 +460,29 @@ test.describe('Settings Navigation', () => {
234460
await expect(page.locator('text=🎬 OMDB Settings').first()).toBeVisible()
235461
})
236462

463+
test('should navigate to all settings sections', async ({ page }) => {
464+
await navigateToAppSettings(page)
465+
await page.waitForSelector('text=OMDB Settings')
466+
467+
// Navigate to IOT settings
468+
const iotMenuItem = page.getByText('Device Availability')
469+
await iotMenuItem.click()
470+
await expect(page).toHaveURL(/\/app-settings\/iot/)
471+
await expect(page.locator('text=📡 IOT Device Availability').first()).toBeVisible()
472+
473+
// Navigate to AI settings
474+
const aiMenuItem = page.getByText('Ollama Settings')
475+
await aiMenuItem.click()
476+
await expect(page).toHaveURL(/\/app-settings\/ai/)
477+
await expect(page.locator('text=🤖 Ollama Integration').first()).toBeVisible()
478+
479+
// Navigate back to OMDB
480+
const omdbMenuItem = page.getByText('OMDB Settings')
481+
await omdbMenuItem.click()
482+
await expect(page).toHaveURL(/\/app-settings\/omdb/)
483+
await expect(page.locator('text=🎬 OMDB Settings').first()).toBeVisible()
484+
})
485+
237486
test('should highlight active menu item', async ({ page }) => {
238487
await navigateToAppSettings(page)
239488
await page.waitForSelector('text=OMDB Settings')

frontend/src/components/settings-sidebar/settings-menu-item.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,7 @@ export const SettingsMenuItem = Shade<SettingsMenuItemProps>({
1414
render: ({ props, injector, useObservable }) => {
1515
const { icon, label, href, routingOptions } = props
1616

17-
const [currentPath] = useObservable(
18-
'locationChange',
19-
injector.getInstance(LocationService).onLocationPathChanged,
20-
)
17+
const [currentPath] = useObservable('locationChange', injector.getInstance(LocationService).onLocationPathChanged)
2118
const isActive = !!match(href, routingOptions)(currentPath)
2219

2320
return (

0 commit comments

Comments
 (0)