Skip to content

Commit

Permalink
Migrate tests to playwright
Browse files Browse the repository at this point in the history
  • Loading branch information
zerodevx committed Feb 24, 2023
1 parent 75af6f1 commit 131c855
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 22 deletions.
7 changes: 6 additions & 1 deletion playwright.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import check from 'is-port-reachable'

const dev = await check(5173, { host: 'localhost' })

/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
port: dev ? 5173 : 4173,
reuseExistingServer: dev
},
testDir: 'tests'
}
Expand Down
1 change: 1 addition & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare global {
var PUBLIC_VERSION
var toast
var gtag
var TEST_MODE
}

export {}
22 changes: 12 additions & 10 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import DummyComponent from './Dummy.svelte'
import camelCase from 'camelcase'
import Prism from 'prismjs'
// Hoist to `window` for debug
// Hoist to `window` for tests
if (browser) window.toast = toast
const version = PUBLIC_VERSION
Expand Down Expand Up @@ -278,12 +278,11 @@ toast.pop(0)`,
'--toastBorderRadius': '1rem'
}
})
// @ts-ignore
if (window.Cypress) {
if (window.TEST_MODE) {
toast.set(id, {
component: {
src: DummyComponent,
props: { title: 'Test Reactivity' },
props: { title: 'test reactivity' },
sendIdTo: 'toastId'
}
})
Expand Down Expand Up @@ -347,8 +346,7 @@ toast.pop(0)`,
run: () =>
toast.push('Say cheese!', {
theme: {
// @ts-ignore
'--toastBtnContent': window.Cypress
'--toastBtnContent': window.TEST_MODE
? `'x'`
: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24px' height='24px' fill='%23F1CB30' viewBox='0 0 512 512' xml:space='preserve'%3E%3Cpath d='M256,0C114.842,0,0,114.842,0,256s114.842,256,256,256s256-114.842,256-256S397.158,0,256,0z'/%3E%3Cg%3E%3Cpath style='fill:%2357575C;' d='M355.297,175.321c-8.161,0-16.167,3.305-21.938,9.092c-5.773,5.772-9.092,13.762-9.092,21.938 c0,8.163,3.32,16.168,9.092,21.94c5.772,5.772,13.777,9.09,21.938,9.09c8.161,0,16.167-3.32,21.938-9.09 c5.773-5.772,9.092-13.777,9.092-21.94c0-8.176-3.32-16.167-9.092-21.938C371.464,178.626,363.472,175.321,355.297,175.321z'/%3E%3Cpath style='fill:%2357575C;' d='M178.641,228.291c5.773-5.772,9.092-13.762,9.092-21.94c0-8.176-3.32-16.167-9.092-21.938 c-5.772-5.787-13.777-9.092-21.938-9.092c-8.161,0-16.167,3.305-21.938,9.092c-5.772,5.772-9.092,13.762-9.092,21.938 c0,8.176,3.32,16.168,9.092,21.94c5.772,5.786,13.777,9.09,21.938,9.09C164.864,237.382,172.87,234.077,178.641,228.291z'/%3E%3C/g%3E%3Cpath style='fill:%23DF6246;' d='M356.49,326.085c-3.603-8.696-12.088-14.367-21.501-14.367H256h-78.991 c-9.413,0-17.898,5.671-21.501,14.367c-3.601,8.696-1.61,18.708,5.046,25.363c25.495,25.493,59.392,39.534,95.446,39.534 s69.952-14.041,95.446-39.534C358.102,344.792,360.093,334.78,356.49,326.085z'/%3E%3Cpath style='fill:%23E69629;' d='M160.552,351.448c-6.656-6.654-8.647-16.665-5.046-25.363c3.603-8.696,12.088-14.367,21.501-14.367 H256V0C114.842,0,0,114.842,0,256s114.842,256,256,256V390.982C219.946,390.982,186.048,376.941,160.552,351.448z M125.673,206.352 c0-8.176,3.32-16.167,9.092-21.938c5.772-5.787,13.777-9.092,21.938-9.092c8.161,0,16.167,3.305,21.938,9.092 c5.773,5.772,9.092,13.762,9.092,21.938c0,8.176-3.32,16.168-9.092,21.94c-5.772,5.786-13.777,9.09-21.938,9.09 c-8.161,0-16.167-3.305-21.938-9.09C128.993,222.52,125.673,214.528,125.673,206.352z'/%3E%3Cpath style='fill:%23DD512A;' d='M177.009,311.718c-9.413,0-17.898,5.671-21.501,14.367c-3.601,8.696-1.61,18.708,5.046,25.363 c25.495,25.493,59.39,39.534,95.445,39.534v-79.264H177.009z'/%3E%3C/svg%3E")`
}
Expand Down Expand Up @@ -387,8 +385,9 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
</div>
<p class="max-w-2xl mx-auto text-center mb-6">
Simple elegant toast notifications for modern web frontends in very little lines of code.
Because a demo helps better than a thousand API docs, so here it is. Use in Vanilla JS (8kb
gzipped) or as a Svelte component.
Because a demo helps better than a thousand API docs, so here it is. Use in Vanilla JS <span
class="font-mono text-sm">(8kB gzipped)</span
> or as a Svelte component.
</p>
<div class="mockup-code h-80 mb-4 text-sm overflow-auto">
<pre><code class="language-javascript">{@html formatted}</code></pre>
Expand All @@ -401,7 +400,7 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
on:click={() => {
clicked(btn)
}}
data-btn={camelCase(btn.name)}>{btn.name}</button
data-testid={camelCase(btn.name)}>{btn.name}</button
>
{/each}
</div>
Expand All @@ -415,11 +414,14 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
<SvelteToast {options} />
</div>
<style>
<style lang="postcss">
:global(.custom) {
--toastBackground: #4299e1;
--toastBarBackground: #2b6cb0;
}
.btn.selected {
@apply opacity-80;
}
.colors {
--toastBackground: rgba(245, 208, 254, 0.95);
--toastColor: #424242;
Expand Down
7 changes: 4 additions & 3 deletions src/routes/Dummy.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ const declined = () => clicked(false)
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
</p>
<div class="h-12 flex flex-row justify-around">
<button class="w-28 h-10 rounded-full" on:click={declined}>DECLINE</button>
<button class="w-28 h-10 rounded-full" on:click={accepted} data-btn="dummyAccept">ACCEPT</button
<div class="h-10 flex flex-row justify-around">
<button class="btn btn-sm" on:click={declined}>DECLINE</button>
<button class="btn btn-sm btn-primary" on:click={accepted} data-testid="dummyAccept"
>ACCEPT</button
>
</div>
</div>
12 changes: 10 additions & 2 deletions svelte.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import preprocess from 'svelte-preprocess'
import adapter from '@sveltejs/adapter-static'

const dev = process.argv.includes('dev')

/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter()
adapter: adapter(),
paths: {
base: dev ? '' : '/svelte-toast'
}
},
compilerOptions: {
dev,
css: 'external'
},

preprocess: [
preprocess({
postcss: true
Expand Down
255 changes: 250 additions & 5 deletions tests/test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,251 @@
import { expect, test } from '@playwright/test';
import { expect, test } from '@playwright/test'

test('index page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});
const sleep = (t) => new Promise((r) => setTimeout(r, t))

test('displays a toast', async ({ page }) => {
await page.goto('/')
await page.getByTestId('default').click()
await expect(page.locator('._toastItem')).toBeVisible()
})

test('displays coloured toast', async ({ page }) => {
await page.goto('/')
await page.getByTestId('coloredToast').click()
await expect(page.locator('._toastItem')).toHaveCSS('background-color', 'rgba(72, 187, 120, 0.9)')
})

test('displays rich html', async ({ page }) => {
await page.goto('/')
await page.getByTestId('richHtml').click()
await expect(page.locator('._toastItem a')).toHaveCount(1)
})

test('can change duration', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' })
const id = await page.evaluate(`window.toast.push('test',{duration:100})`)
expect(id).toBe(1)
await expect(page.locator('._toastItem')).toBeVisible()
await sleep(200)
await expect(page.locator('._toastItem')).toHaveCount(0)
})

test('can be non-dismissable then popped', async ({ page }) => {
await page.goto('/')
await page.getByTestId('nonDismissable').click()
await expect(page.locator('._toastItem')).toBeVisible()
await expect(page.locator('._toastBtn')).toHaveCount(0)
await page.getByTestId('removeLastToast').click()
await expect(page.locator('._toastItem')).toHaveCount(0)
})

test('flips progress bar', async ({ page }) => {
await page.goto('/')
await page.getByTestId('flipProgressBar').click()
const v0 = parseFloat(await page.locator('._toastBar').getAttribute('value'))
await sleep(100)
const v1 = parseFloat(await page.locator('._toastBar').getAttribute('value'))
expect(v1).toBeGreaterThan(v0)
})

test('dynamically updates progress bar', async ({ page }) => {
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
await page.goto('/', { waitUntil: 'networkidle' })
const id = await page.evaluate(`window.toast.push('test',{duration:1,initial:0,next:0})`)
expect(await get()).toBe(0)
await page.evaluate(`window.toast.set(${id},{next:0.2})`)
await sleep(50)
expect(await get()).toBe(0.2)
await page.evaluate(`window.toast.set(${id},{next:1})`)
await sleep(50)
await expect(page.locator('._toastItem')).toHaveCount(0)
})

test('changes default colors', async ({ page }) => {
await page.goto('/')
await page.getByTestId('changeDefaultColors').click()
await expect(page.locator('._toastItem')).toHaveCSS(
'background-color',
'rgba(245, 208, 254, 0.95)'
)
})

test('positions to bottom, then restore defaults', async ({ page }) => {
await page.goto('/')
await page.getByTestId('positionToBottom').click()
await expect(page.locator('._toastItem')).toHaveCSS('bottom', '0px')
await page.locator('._toastBtn').click()
await expect(page.locator('._toastItem')).toHaveCount(0)
await page.getByTestId('restoreDefaults').click()
await expect(page.locator('._toastItem')).toHaveCSS('right', '0px')
})

test('clears all active toasts', async ({ page }) => {
await page.goto('/')
for (let a = 0; a < 3; a++) {
await page.getByTestId('default').click()
}
await expect(page.locator('._toastItem')).toHaveCount(3)
await page.evaluate(`window.toast.pop(0)`)
await expect(page.locator('._toastItem')).toHaveCount(0)
})

test('pushes to correct container target', async ({ page }) => {
await page.goto('/')
await page.getByTestId('createNewToastContainer').click()
await expect(page.locator('._toastItem')).toHaveCSS('top', '0px')
})

test('removes all toast from particular container', async ({ page }) => {
await page.goto('/')
for (let a = 0; a < 3; a++) {
await page.getByTestId('createNewToastContainer').click()
}
await page.getByTestId('default').click()
await expect(page.locator('._toastItem')).toHaveCount(4)
await page.getByTestId('removeAllToastsFromContainer').click()
await expect(page.locator('._toastItem')).toHaveCount(1)
})

test('renders custom component and is reactive', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' })
await page.getByTestId('sendComponentAsAMessage').click()
await expect(page.locator('._toastItem h1')).toHaveText('A Dummy Cookie Component')
await page.getByTestId('removeLastToast').click()
await expect(page.locator('._toastItem')).toHaveCount(0)
await page.evaluate(`window.TEST_MODE=true`)
await page.getByTestId('sendComponentAsAMessage').click()
await expect(page.getByText('test reactivity')).toBeVisible()
await page.getByTestId('default').click()
await page.getByTestId('dummyAccept').click()
await expect(page.locator('._toastItem h1')).toHaveCount(0)
})

test('pauses on mouse hover', async ({ page }) => {
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
await page.goto('/')
await page.getByTestId('pauseOnMouseHover').click()
await page.locator('._toastItem').hover()
const v0 = await get()
await sleep(50)
const v1 = await get()
expect(v0).toEqual(v1)
await page.mouse.move(0, 0)
await sleep(50)
const v2 = await get()
expect(v2).toBeLessThan(v1)
})

test('does not pause when `pausable` is false', async ({ page }) => {
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
await page.goto('/')
await page.getByTestId('default').click()
await page.locator('._toastItem').hover({ force: true })
const v0 = await get()
await sleep(50)
const v1 = await get()
expect(v0).toBeGreaterThan(v1)
})

test('passes pausable edge case when `next` is changed on hover', async ({ page }) => {
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
await page.goto('/', { waitUntil: 'networkidle' })
const id = await page.evaluate(`window.toast.push('test',{pausable:true,duration:50})`)
await page.locator('._toastItem').hover({ force: true })
await page.evaluate(`window.toast.set(${id},{next:0.1})`)
await sleep(100)
expect(await get()).toBe(0.1)
await sleep(50)
expect(await get()).toBe(0.1)
})

test('runs callback when popped', async ({ page }) => {
await page.goto('/')
await page.getByTestId('runCallbackOnToastRemoval').click()
await expect(page.locator('._toastItem')).toHaveText('Wait for it...')
await page.locator('._toastBtn').click()
await expect(page.locator('._toastItem')).toContainText('callback has been executed')
})

test('runs callback when popped programatically', async ({ page }) => {
await page.goto('/')
await page.getByTestId('runCallbackOnToastRemoval').click()
await expect(page.locator('._toastItem')).toHaveText('Wait for it...')
await page.evaluate(`window.toast.pop(0)`)
await expect(page.locator('._toastItem')).toContainText('callback has been executed')
})

test('adds and merges user-defined classes', async ({ page }) => {
await page.goto('/')
await page.getByTestId('styleWithUserDefinedClasses').click()
await expect(page.locator('._toastItem')).toHaveCSS('background-color', 'rgb(66, 153, 225)')
await expect(page.locator('._toastContainer li')).toHaveClass(
/(?=.*custom)(?=.*merge1)(?=.*merge2)/
)
})

test('can change dismiss btn char', async ({ page }) => {
await page.goto('/')
await page.evaluate(`window.TEST_MODE=true`)
await page.getByTestId('customDismissButton').click()
const btn = await page
.locator('._toastBtn')
.evaluate((e) => window.getComputedStyle(e, ':after').content)
expect(btn).toBe('"x"')
})

// Playwright currently does not provide a way to test this
// https://github.com/microsoft/playwright/issues/2286
/*
it('Toggles pause and resume on visibilitychange', () => {
cy.get('[data-btn=default]')
.click()
.document()
.then((doc) => {
cy.stub(doc, 'hidden').value(true)
})
.document()
.trigger('visibilitychange')
.get('._toastBar')
.then(($bar) => {
const old = parseFloat($bar.val())
cy.wait(500).then(() => {
expect(parseFloat($bar.val())).to.be.equal(old)
})
})
.document()
.then((doc) => {
cy.stub(doc, 'hidden').value(false)
})
.document()
.trigger('visibilitychange')
.get('._toastBar')
.then(($bar) => {
const old = parseFloat($bar.val())
cy.wait(500).then(() => {
expect(parseFloat($bar.val())).to.be.below(old)
})
})
.get('._toastBtn')
.click()
})
*/

// Backward compatibility tests

test('`progress` key still works', async ({ page }) => {
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
await page.goto('/', { waitUntil: 'networkidle' })
const id = await page.evaluate(`window.toast.push('test',{duration:1,initial:0,progress:0})`)
expect(await get()).toBe(0)
await page.evaluate(`window.toast.set(${id},{progress:0.2})`)
await sleep(50)
expect(await get()).toBe(0.2)
})

test('`push()` accepts both string and obj', async ({ page }) => {
await page.goto('/', { waitUntil: 'networkidle' })
await page.evaluate(`window.toast.push('push with string')`)
await expect(page.getByText('push with string')).toBeVisible()
await page.evaluate(`window.toast.push({msg:'push with obj'})`)
await expect(page.getByText('push with obj')).toBeVisible()
})
Loading

0 comments on commit 131c855

Please sign in to comment.