diff --git a/.gitignore b/.gitignore index 5a5ed98..441fdc9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* yarn-error.log +test-results/ \ No newline at end of file diff --git a/src/lib/style/app.scss b/src/lib/style/app.scss index 7a40989..5b996ef 100644 --- a/src/lib/style/app.scss +++ b/src/lib/style/app.scss @@ -80,7 +80,7 @@ nav { margin-bottom: 3px; } -#btcclock-wrapper { +#btclock-wrapper { margin: 0 auto; } @@ -158,8 +158,16 @@ nav { font-size: 3rem; padding-left: 5px; padding-right: 5px; - padding-top: 20px !important; - padding-bottom: 20px !important; + @include media-breakpoint-up(sm) { + font-size: 1.8rem; + padding-top: 13px !important; + padding-bottom: 13px !important; + } + @include media-breakpoint-up(xxl) { + font-size: 3rem; + padding-top: 29px !important; + padding-bottom: 29px !important; + } } .digit { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index df4e0de..7f4684d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -62,7 +62,7 @@ {#if !$isLoading} - + {getFlagEmoji($locale)} {languageNames[$locale]} {#each $locales as locale} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8377a2a..2f13bbe 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -24,7 +24,15 @@ leds: [] }); - onMount(() => { + const fetchStatusData = () => { + fetch(`${PUBLIC_BASE_URL}/api/status`) + .then((res) => res.json()) + .then((data) => { + status.set(data); + }); + }; + + const fetchSettingsData = () => { fetch(PUBLIC_BASE_URL + `/api/settings`) .then((res) => res.json()) .then((data) => { @@ -41,12 +49,11 @@ } settings.set(data); }); + }; - fetch(`${PUBLIC_BASE_URL}/api/status`) - .then((res) => res.json()) - .then((data) => { - status.set(data); - }); + onMount(() => { + fetchSettingsData(); + fetchStatusData(); const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`); @@ -75,7 +82,7 @@ - +
diff --git a/src/routes/Rendered.svelte b/src/routes/Rendered.svelte index c763d4a..d46a7cd 100644 --- a/src/routes/Rendered.svelte +++ b/src/routes/Rendered.svelte @@ -6,7 +6,7 @@ }; -
+
{#each status.data as char} {#if isSplitText(char)} @@ -15,6 +15,8 @@
{part}
{/each}
+ {:else if char.length >= 3} +
{char}
{:else if char.length === 0 || char === ' '}
  
{:else} diff --git a/src/routes/Settings.svelte b/src/routes/Settings.svelte index 7854fb7..e56364d 100644 --- a/src/routes/Settings.svelte +++ b/src/routes/Settings.svelte @@ -37,6 +37,11 @@ const dispatch = createEventDispatcher(); + const handleReset = (e: Event) => { + e.preventDefault(); + dispatch('formReset'); + }; + const onSave = async (e: Event) => { e.preventDefault(); let formSettings = $settings; @@ -112,7 +117,13 @@ - + {$_('time.minutes')} @@ -123,7 +134,13 @@ > - + {$_('time.minutes')} @@ -134,7 +151,13 @@ > - + {$_('time.seconds')} {$_('section.settings.shortAmountsWarning')} @@ -300,7 +323,7 @@ {/each} {/if} - + diff --git a/src/routes/Status.svelte b/src/routes/Status.svelte index 640df60..fb3b022 100644 --- a/src/routes/Status.svelte +++ b/src/routes/Status.svelte @@ -106,6 +106,7 @@ {$_('section.status.screenCycle')}: { await page.route('*/**/api/status', async (route) => { - const json = { - currentScreen: 0, - numScreens: 7, - timerRunning: true, - espUptime: 4479, - espFreeHeap: 58508, - espHeapSize: 342108, - connectionStatus: { price: true, blocks: true }, - rssi: -66, - data: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'], - rendered: ['BLOCK/HEIGHT', '8', '1', '8', '0', '2', '6'], - leds: [ - { red: 0, green: 0, blue: 0, hex: '#000000' }, - { red: 0, green: 0, blue: 0, hex: '#000000' }, - { red: 0, green: 0, blue: 0, hex: '#000000' }, - { red: 0, green: 0, blue: 0, hex: '#000000' } - ] - }; - await route.fulfill({ json }); + await route.fulfill({ json: statusJson }); }); await page.route('*/**/api/settings', async (route) => { const json = { @@ -60,24 +61,117 @@ test.beforeEach(async ({ page }) => { }; await route.fulfill({ json }); }); + + await page.route('**/events', (route) => { + //statusJson.data = ['S', 'S', 'E', 'V', 'E', 'N', 'T']; + + // Respond with a custom SSE message + route.fulfill({ + status: 200, + contentType: 'text/event-stream', + body: `data: ${JSON.stringify(statusJson)}\n\n` + }); + }); }); -test('index page has expected status', async ({ page }) => { +test('index page has expected columns control, status, settings', async ({ page }) => { await page.goto('/'); + await expect(page.getByRole('heading', { name: 'Control' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Status' })).toBeVisible(); -}); - -test('index page has expected settings', async ({ page }) => { - await page.goto('/'); await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible(); }); -test('index page has expected control', async ({ page }) => { +test('index page has working language selector', async ({ page }) => { await page.goto('/'); - await expect(page.getByRole('heading', { name: 'Control' })).toBeVisible(); + await expect(page.locator('//*[@id="nav-language-dropdown"]/a')).toBeVisible(); + page.locator('//*[@id="nav-language-dropdown"]/a').click(); + await expect(page.locator('//*[@id="nav-language-dropdown"]/div/button[1]')).toBeVisible(); + page.locator('//*[@id="nav-language-dropdown"]/div/button[2]').click(); + await expect(page.getByRole('heading', { name: 'Instellingen' })).toBeVisible(); + page.locator('//*[@id="nav-language-dropdown"]/a').click(); + page.locator('//*[@id="nav-language-dropdown"]/div/button[3]').click(); + await expect(page.getByRole('heading', { name: 'ConfiguraciĆ³n' })).toBeVisible(); }); test('api page has expected load button', async ({ page }) => { await page.goto('/api'); await expect(page.getByRole('button', { name: 'Load' })).toBeVisible(); }); + +test('timezone can be negative, zero and positive', async ({ page }) => { + await page.goto('/'); + const tzOffsetField = 'input#tzOffset'; + + for (const val of ['-10', '0', '42']) { + await page.fill(tzOffsetField, val); + const resultValue = await page.$eval(tzOffsetField, (input: HTMLInputElement) => input.value); + expect(resultValue).toBe(val); + await page.getByRole('button', { name: 'Save' }).click(); + } +}); + +test('time values can not be zero or negative', async ({ page }) => { + await page.goto('/'); + + for (const field of ['#timePerScreen', '#fullRefreshMin', '#minSecPriceUpd']) { + for (const val of ['42', '210']) { + await page.fill(field, val); + const resultValue = await page.$eval(field, (input: HTMLInputElement) => input.value); + expect(resultValue).toBe(val); + await page.getByRole('button', { name: 'Save' }).click(); + const validationMessage = await page.$eval( + field, + (input: HTMLInputElement) => input.validationMessage + ); + expect(validationMessage).not.toContain('Value must be greater'); + } + + for (const val of ['-10', '0']) { + await page.fill(field, val); + const resultValue = await page.$eval(field, (input: HTMLInputElement) => input.value); + expect(resultValue).toBe(val); + await page.getByRole('button', { name: 'Save' }).click(); + const validationMessage = await page.$eval( + field, + (input: HTMLInputElement) => input.validationMessage + ); + expect(validationMessage).toContain('Value must be greater'); + } + } +}); + +test('info message when fetch eur price is enabled', async ({ page }) => { + await page.goto('/'); + const inputField = 'input#fetchEurPrice'; + const switchElement = await page.$(inputField); + + expect(switchElement).toBeTruthy(); + const isSwitchEnabled = await switchElement.isChecked(); + expect(isSwitchEnabled).toBe(false); + + await expect(page.getByText('the WS Price connection will show')).toBeHidden(); + + await switchElement.click(); + const isSwitchNowEnabled = await switchElement.isChecked(); + expect(isSwitchNowEnabled).toBe(true); + + await expect(page.getByText('the WS Price connection will show')).toBeVisible(); +}); + +test('parse all types of EPD content correctly', async ({ page }) => { + statusJson.data[2] = '123'; + + await page.goto('/'); + + await expect(page.getByRole('heading', { name: 'Status' })).toBeVisible(); + await page.waitForSelector('#timerStatusText:has-text("running")'); + await page.waitForSelector('#btclock-wrapper > div > div:nth-child(1)'); + + expect(statusJson.data[0]).toContain('/'); + await expect(page.locator('#btclock-wrapper > div > div:nth-child(1)')).toBeTruthy(); + await expect(page.locator('#btclock-wrapper > div > div:nth-child(1)')).toHaveClass('splitText'); + expect(statusJson.data[1]).toHaveLength(1); + await expect(page.locator('#btclock-wrapper > div > div:nth-child(2)')).toHaveClass('digit'); + expect(statusJson.data[2]).toHaveLength(3); + await expect(page.locator('#btclock-wrapper > div > div:nth-child(3)')).toHaveClass('mediumText'); +});