Write more tests

This commit is contained in:
Djuri Baars 2023-11-25 00:42:37 +01:00
parent 60360ef76b
commit 2a50c76cc0
8 changed files with 178 additions and 42 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ dist
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
yarn-error.log yarn-error.log
test-results/

View file

@ -80,7 +80,7 @@ nav {
margin-bottom: 3px; margin-bottom: 3px;
} }
#btcclock-wrapper { #btclock-wrapper {
margin: 0 auto; margin: 0 auto;
} }
@ -158,8 +158,16 @@ nav {
font-size: 3rem; font-size: 3rem;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
padding-top: 20px !important; @include media-breakpoint-up(sm) {
padding-bottom: 20px !important; 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 { .digit {

View file

@ -62,7 +62,7 @@
</NavItem> </NavItem>
</Nav> </Nav>
{#if !$isLoading} {#if !$isLoading}
<Dropdown inNavbar> <Dropdown id="nav-language-dropdown" inNavbar>
<DropdownToggle nav caret>{getFlagEmoji($locale)} {languageNames[$locale]}</DropdownToggle> <DropdownToggle nav caret>{getFlagEmoji($locale)} {languageNames[$locale]}</DropdownToggle>
<DropdownMenu end> <DropdownMenu end>
{#each $locales as locale} {#each $locales as locale}

View file

@ -24,7 +24,15 @@
leds: [] 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`) fetch(PUBLIC_BASE_URL + `/api/settings`)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
@ -41,12 +49,11 @@
} }
settings.set(data); settings.set(data);
}); });
};
fetch(`${PUBLIC_BASE_URL}/api/status`) onMount(() => {
.then((res) => res.json()) fetchSettingsData();
.then((data) => { fetchStatusData();
status.set(data);
});
const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`); const evtSource = new EventSource(`${PUBLIC_BASE_URL}/events`);
@ -75,7 +82,7 @@
<Row> <Row>
<Control bind:settings bind:status></Control> <Control bind:settings bind:status></Control>
<Status bind:settings bind:status></Status> <Status bind:settings bind:status></Status>
<Settings bind:settings on:showToast={showToast}></Settings> <Settings bind:settings on:showToast={showToast} on:formReset={fetchSettingsData}></Settings>
</Row> </Row>
</Container> </Container>
<div class="position-fixed bottom-0 end-0 p-2"> <div class="position-fixed bottom-0 end-0 p-2">

View file

@ -6,7 +6,7 @@
}; };
</script> </script>
<div class="btcclock-wrapper" id="btcclock-wrapper"> <div class="btclock-wrapper" id="btclock-wrapper">
<div class="btclock"> <div class="btclock">
{#each status.data as char} {#each status.data as char}
{#if isSplitText(char)} {#if isSplitText(char)}
@ -15,6 +15,8 @@
<div class="flex-items">{part}</div> <div class="flex-items">{part}</div>
{/each} {/each}
</div> </div>
{:else if char.length >= 3}
<div class="mediumText">{char}</div>
{:else if char.length === 0 || char === ' '} {:else if char.length === 0 || char === ' '}
<div class="digit">&nbsp;&nbsp;</div> <div class="digit">&nbsp;&nbsp;</div>
{:else} {:else}

View file

@ -37,6 +37,11 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const handleReset = (e: Event) => {
e.preventDefault();
dispatch('formReset');
};
const onSave = async (e: Event) => { const onSave = async (e: Event) => {
e.preventDefault(); e.preventDefault();
let formSettings = $settings; let formSettings = $settings;
@ -112,7 +117,13 @@
<Label md={6} for="timePerScreen" size="sm">{$_('section.settings.timePerScreen')}</Label> <Label md={6} for="timePerScreen" size="sm">{$_('section.settings.timePerScreen')}</Label>
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.timePerScreen} /> <Input
type="number"
id="timePerScreen"
min={1}
step="1"
bind:value={$settings.timePerScreen}
/>
<InputGroupText>{$_('time.minutes')}</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
</InputGroup> </InputGroup>
</Col> </Col>
@ -123,7 +134,13 @@
> >
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.fullRefreshMin} /> <Input
type="number"
id="fullRefreshMin"
min={1}
step="1"
bind:value={$settings.fullRefreshMin}
/>
<InputGroupText>{$_('time.minutes')}</InputGroupText> <InputGroupText>{$_('time.minutes')}</InputGroupText>
</InputGroup> </InputGroup>
</Col> </Col>
@ -134,7 +151,13 @@
> >
<Col md="6"> <Col md="6">
<InputGroup size="sm"> <InputGroup size="sm">
<Input type="number" min={1} step="1" bind:value={$settings.minSecPriceUpd} /> <Input
type="number"
id="minSecPriceUpd"
min={1}
step="1"
bind:value={$settings.minSecPriceUpd}
/>
<InputGroupText>{$_('time.seconds')}</InputGroupText> <InputGroupText>{$_('time.seconds')}</InputGroupText>
</InputGroup> </InputGroup>
<FormText>{$_('section.settings.shortAmountsWarning')}</FormText> <FormText>{$_('section.settings.shortAmountsWarning')}</FormText>
@ -300,7 +323,7 @@
{/each} {/each}
{/if} {/if}
</Row> </Row>
<Button type="reset" color="secondary">{$_('button.reset')}</Button> <Button on:click={handleReset} color="secondary">{$_('button.reset')}</Button>
<Button color="primary">{$_('button.save')}</Button> <Button color="primary">{$_('button.save')}</Button>
</Form> </Form>
</CardBody> </CardBody>

View file

@ -106,6 +106,7 @@
</section> </section>
{$_('section.status.screenCycle')}: {$_('section.status.screenCycle')}:
<a <a
id="timerStatusText"
href={'#'} href={'#'}
style="cursor: pointer" style="cursor: pointer"
tabindex="0" tabindex="0"

View file

@ -1,8 +1,6 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
test.beforeEach(async ({ page }) => { const statusJson = {
await page.route('*/**/api/status', async (route) => {
const json = {
currentScreen: 0, currentScreen: 0,
numScreens: 7, numScreens: 7,
timerRunning: true, timerRunning: true,
@ -20,7 +18,10 @@ test.beforeEach(async ({ page }) => {
{ red: 0, green: 0, blue: 0, hex: '#000000' } { red: 0, green: 0, blue: 0, hex: '#000000' }
] ]
}; };
await route.fulfill({ json });
test.beforeEach(async ({ page }) => {
await page.route('*/**/api/status', async (route) => {
await route.fulfill({ json: statusJson });
}); });
await page.route('*/**/api/settings', async (route) => { await page.route('*/**/api/settings', async (route) => {
const json = { const json = {
@ -60,24 +61,117 @@ test.beforeEach(async ({ page }) => {
}; };
await route.fulfill({ json }); 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 page.goto('/');
await expect(page.getByRole('heading', { name: 'Control' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Status' })).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(); 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 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 }) => { test('api page has expected load button', async ({ page }) => {
await page.goto('/api'); await page.goto('/api');
await expect(page.getByRole('button', { name: 'Load' })).toBeVisible(); 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');
});