forked from btclock/webui
Write more tests
This commit is contained in:
parent
60360ef76b
commit
2a50c76cc0
8 changed files with 178 additions and 42 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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/
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"> </div>
|
<div class="digit"> </div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
118
tests/test.ts
118
tests/test.ts
|
@ -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,
|
||||||
|
@ -19,8 +17,11 @@ test.beforeEach(async ({ page }) => {
|
||||||
{ 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 });
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue