Skip to content

Commit 131c855

Browse files
committed
Migrate tests to playwright
1 parent 75af6f1 commit 131c855

File tree

7 files changed

+290
-22
lines changed

7 files changed

+290
-22
lines changed

playwright.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import check from 'is-port-reachable'
2+
3+
const dev = await check(5173, { host: 'localhost' })
4+
15
/** @type {import('@playwright/test').PlaywrightTestConfig} */
26
const config = {
37
webServer: {
48
command: 'npm run build && npm run preview',
5-
port: 4173
9+
port: dev ? 5173 : 4173,
10+
reuseExistingServer: dev
611
},
712
testDir: 'tests'
813
}

src/app.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare global {
1010
var PUBLIC_VERSION
1111
var toast
1212
var gtag
13+
var TEST_MODE
1314
}
1415

1516
export {}

src/routes/+page.svelte

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import DummyComponent from './Dummy.svelte'
99
import camelCase from 'camelcase'
1010
import Prism from 'prismjs'
1111
12-
// Hoist to `window` for debug
12+
// Hoist to `window` for tests
1313
if (browser) window.toast = toast
1414
1515
const version = PUBLIC_VERSION
@@ -278,12 +278,11 @@ toast.pop(0)`,
278278
'--toastBorderRadius': '1rem'
279279
}
280280
})
281-
// @ts-ignore
282-
if (window.Cypress) {
281+
if (window.TEST_MODE) {
283282
toast.set(id, {
284283
component: {
285284
src: DummyComponent,
286-
props: { title: 'Test Reactivity' },
285+
props: { title: 'test reactivity' },
287286
sendIdTo: 'toastId'
288287
}
289288
})
@@ -347,8 +346,7 @@ toast.pop(0)`,
347346
run: () =>
348347
toast.push('Say cheese!', {
349348
theme: {
350-
// @ts-ignore
351-
'--toastBtnContent': window.Cypress
349+
'--toastBtnContent': window.TEST_MODE
352350
? `'x'`
353351
: `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")`
354352
}
@@ -387,8 +385,9 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
387385
</div>
388386
<p class="max-w-2xl mx-auto text-center mb-6">
389387
Simple elegant toast notifications for modern web frontends in very little lines of code.
390-
Because a demo helps better than a thousand API docs, so here it is. Use in Vanilla JS (8kb
391-
gzipped) or as a Svelte component.
388+
Because a demo helps better than a thousand API docs, so here it is. Use in Vanilla JS <span
389+
class="font-mono text-sm">(8kB gzipped)</span
390+
> or as a Svelte component.
392391
</p>
393392
<div class="mockup-code h-80 mb-4 text-sm overflow-auto">
394393
<pre><code class="language-javascript">{@html formatted}</code></pre>
@@ -401,7 +400,7 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
401400
on:click={() => {
402401
clicked(btn)
403402
}}
404-
data-btn={camelCase(btn.name)}>{btn.name}</button
403+
data-testid={camelCase(btn.name)}>{btn.name}</button
405404
>
406405
{/each}
407406
</div>
@@ -415,11 +414,14 @@ $: formatted = Prism.highlight(code, Prism.languages.javascript, 'javascript')
415414
<SvelteToast {options} />
416415
</div>
417416
418-
<style>
417+
<style lang="postcss">
419418
:global(.custom) {
420419
--toastBackground: #4299e1;
421420
--toastBarBackground: #2b6cb0;
422421
}
422+
.btn.selected {
423+
@apply opacity-80;
424+
}
423425
.colors {
424426
--toastBackground: rgba(245, 208, 254, 0.95);
425427
--toastColor: #424242;

src/routes/Dummy.svelte

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ const declined = () => clicked(false)
2929
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
3030
laboris nisi ut aliquip ex ea commodo consequat.
3131
</p>
32-
<div class="h-12 flex flex-row justify-around">
33-
<button class="w-28 h-10 rounded-full" on:click={declined}>DECLINE</button>
34-
<button class="w-28 h-10 rounded-full" on:click={accepted} data-btn="dummyAccept">ACCEPT</button
32+
<div class="h-10 flex flex-row justify-around">
33+
<button class="btn btn-sm" on:click={declined}>DECLINE</button>
34+
<button class="btn btn-sm btn-primary" on:click={accepted} data-testid="dummyAccept"
35+
>ACCEPT</button
3536
>
3637
</div>
3738
</div>

svelte.config.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import preprocess from 'svelte-preprocess'
22
import adapter from '@sveltejs/adapter-static'
33

4+
const dev = process.argv.includes('dev')
5+
46
/** @type {import('@sveltejs/kit').Config} */
57
const config = {
68
kit: {
7-
adapter: adapter()
9+
adapter: adapter(),
10+
paths: {
11+
base: dev ? '' : '/svelte-toast'
12+
}
13+
},
14+
compilerOptions: {
15+
dev,
16+
css: 'external'
817
},
9-
1018
preprocess: [
1119
preprocess({
1220
postcss: true

tests/test.js

Lines changed: 250 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,251 @@
1-
import { expect, test } from '@playwright/test';
1+
import { expect, test } from '@playwright/test'
22

3-
test('index page has expected h1', async ({ page }) => {
4-
await page.goto('/');
5-
await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
6-
});
3+
const sleep = (t) => new Promise((r) => setTimeout(r, t))
4+
5+
test('displays a toast', async ({ page }) => {
6+
await page.goto('/')
7+
await page.getByTestId('default').click()
8+
await expect(page.locator('._toastItem')).toBeVisible()
9+
})
10+
11+
test('displays coloured toast', async ({ page }) => {
12+
await page.goto('/')
13+
await page.getByTestId('coloredToast').click()
14+
await expect(page.locator('._toastItem')).toHaveCSS('background-color', 'rgba(72, 187, 120, 0.9)')
15+
})
16+
17+
test('displays rich html', async ({ page }) => {
18+
await page.goto('/')
19+
await page.getByTestId('richHtml').click()
20+
await expect(page.locator('._toastItem a')).toHaveCount(1)
21+
})
22+
23+
test('can change duration', async ({ page }) => {
24+
await page.goto('/', { waitUntil: 'networkidle' })
25+
const id = await page.evaluate(`window.toast.push('test',{duration:100})`)
26+
expect(id).toBe(1)
27+
await expect(page.locator('._toastItem')).toBeVisible()
28+
await sleep(200)
29+
await expect(page.locator('._toastItem')).toHaveCount(0)
30+
})
31+
32+
test('can be non-dismissable then popped', async ({ page }) => {
33+
await page.goto('/')
34+
await page.getByTestId('nonDismissable').click()
35+
await expect(page.locator('._toastItem')).toBeVisible()
36+
await expect(page.locator('._toastBtn')).toHaveCount(0)
37+
await page.getByTestId('removeLastToast').click()
38+
await expect(page.locator('._toastItem')).toHaveCount(0)
39+
})
40+
41+
test('flips progress bar', async ({ page }) => {
42+
await page.goto('/')
43+
await page.getByTestId('flipProgressBar').click()
44+
const v0 = parseFloat(await page.locator('._toastBar').getAttribute('value'))
45+
await sleep(100)
46+
const v1 = parseFloat(await page.locator('._toastBar').getAttribute('value'))
47+
expect(v1).toBeGreaterThan(v0)
48+
})
49+
50+
test('dynamically updates progress bar', async ({ page }) => {
51+
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
52+
await page.goto('/', { waitUntil: 'networkidle' })
53+
const id = await page.evaluate(`window.toast.push('test',{duration:1,initial:0,next:0})`)
54+
expect(await get()).toBe(0)
55+
await page.evaluate(`window.toast.set(${id},{next:0.2})`)
56+
await sleep(50)
57+
expect(await get()).toBe(0.2)
58+
await page.evaluate(`window.toast.set(${id},{next:1})`)
59+
await sleep(50)
60+
await expect(page.locator('._toastItem')).toHaveCount(0)
61+
})
62+
63+
test('changes default colors', async ({ page }) => {
64+
await page.goto('/')
65+
await page.getByTestId('changeDefaultColors').click()
66+
await expect(page.locator('._toastItem')).toHaveCSS(
67+
'background-color',
68+
'rgba(245, 208, 254, 0.95)'
69+
)
70+
})
71+
72+
test('positions to bottom, then restore defaults', async ({ page }) => {
73+
await page.goto('/')
74+
await page.getByTestId('positionToBottom').click()
75+
await expect(page.locator('._toastItem')).toHaveCSS('bottom', '0px')
76+
await page.locator('._toastBtn').click()
77+
await expect(page.locator('._toastItem')).toHaveCount(0)
78+
await page.getByTestId('restoreDefaults').click()
79+
await expect(page.locator('._toastItem')).toHaveCSS('right', '0px')
80+
})
81+
82+
test('clears all active toasts', async ({ page }) => {
83+
await page.goto('/')
84+
for (let a = 0; a < 3; a++) {
85+
await page.getByTestId('default').click()
86+
}
87+
await expect(page.locator('._toastItem')).toHaveCount(3)
88+
await page.evaluate(`window.toast.pop(0)`)
89+
await expect(page.locator('._toastItem')).toHaveCount(0)
90+
})
91+
92+
test('pushes to correct container target', async ({ page }) => {
93+
await page.goto('/')
94+
await page.getByTestId('createNewToastContainer').click()
95+
await expect(page.locator('._toastItem')).toHaveCSS('top', '0px')
96+
})
97+
98+
test('removes all toast from particular container', async ({ page }) => {
99+
await page.goto('/')
100+
for (let a = 0; a < 3; a++) {
101+
await page.getByTestId('createNewToastContainer').click()
102+
}
103+
await page.getByTestId('default').click()
104+
await expect(page.locator('._toastItem')).toHaveCount(4)
105+
await page.getByTestId('removeAllToastsFromContainer').click()
106+
await expect(page.locator('._toastItem')).toHaveCount(1)
107+
})
108+
109+
test('renders custom component and is reactive', async ({ page }) => {
110+
await page.goto('/', { waitUntil: 'networkidle' })
111+
await page.getByTestId('sendComponentAsAMessage').click()
112+
await expect(page.locator('._toastItem h1')).toHaveText('A Dummy Cookie Component')
113+
await page.getByTestId('removeLastToast').click()
114+
await expect(page.locator('._toastItem')).toHaveCount(0)
115+
await page.evaluate(`window.TEST_MODE=true`)
116+
await page.getByTestId('sendComponentAsAMessage').click()
117+
await expect(page.getByText('test reactivity')).toBeVisible()
118+
await page.getByTestId('default').click()
119+
await page.getByTestId('dummyAccept').click()
120+
await expect(page.locator('._toastItem h1')).toHaveCount(0)
121+
})
122+
123+
test('pauses on mouse hover', async ({ page }) => {
124+
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
125+
await page.goto('/')
126+
await page.getByTestId('pauseOnMouseHover').click()
127+
await page.locator('._toastItem').hover()
128+
const v0 = await get()
129+
await sleep(50)
130+
const v1 = await get()
131+
expect(v0).toEqual(v1)
132+
await page.mouse.move(0, 0)
133+
await sleep(50)
134+
const v2 = await get()
135+
expect(v2).toBeLessThan(v1)
136+
})
137+
138+
test('does not pause when `pausable` is false', async ({ page }) => {
139+
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
140+
await page.goto('/')
141+
await page.getByTestId('default').click()
142+
await page.locator('._toastItem').hover({ force: true })
143+
const v0 = await get()
144+
await sleep(50)
145+
const v1 = await get()
146+
expect(v0).toBeGreaterThan(v1)
147+
})
148+
149+
test('passes pausable edge case when `next` is changed on hover', async ({ page }) => {
150+
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
151+
await page.goto('/', { waitUntil: 'networkidle' })
152+
const id = await page.evaluate(`window.toast.push('test',{pausable:true,duration:50})`)
153+
await page.locator('._toastItem').hover({ force: true })
154+
await page.evaluate(`window.toast.set(${id},{next:0.1})`)
155+
await sleep(100)
156+
expect(await get()).toBe(0.1)
157+
await sleep(50)
158+
expect(await get()).toBe(0.1)
159+
})
160+
161+
test('runs callback when popped', async ({ page }) => {
162+
await page.goto('/')
163+
await page.getByTestId('runCallbackOnToastRemoval').click()
164+
await expect(page.locator('._toastItem')).toHaveText('Wait for it...')
165+
await page.locator('._toastBtn').click()
166+
await expect(page.locator('._toastItem')).toContainText('callback has been executed')
167+
})
168+
169+
test('runs callback when popped programatically', async ({ page }) => {
170+
await page.goto('/')
171+
await page.getByTestId('runCallbackOnToastRemoval').click()
172+
await expect(page.locator('._toastItem')).toHaveText('Wait for it...')
173+
await page.evaluate(`window.toast.pop(0)`)
174+
await expect(page.locator('._toastItem')).toContainText('callback has been executed')
175+
})
176+
177+
test('adds and merges user-defined classes', async ({ page }) => {
178+
await page.goto('/')
179+
await page.getByTestId('styleWithUserDefinedClasses').click()
180+
await expect(page.locator('._toastItem')).toHaveCSS('background-color', 'rgb(66, 153, 225)')
181+
await expect(page.locator('._toastContainer li')).toHaveClass(
182+
/(?=.*custom)(?=.*merge1)(?=.*merge2)/
183+
)
184+
})
185+
186+
test('can change dismiss btn char', async ({ page }) => {
187+
await page.goto('/')
188+
await page.evaluate(`window.TEST_MODE=true`)
189+
await page.getByTestId('customDismissButton').click()
190+
const btn = await page
191+
.locator('._toastBtn')
192+
.evaluate((e) => window.getComputedStyle(e, ':after').content)
193+
expect(btn).toBe('"x"')
194+
})
195+
196+
// Playwright currently does not provide a way to test this
197+
// https://github.com/microsoft/playwright/issues/2286
198+
/*
199+
it('Toggles pause and resume on visibilitychange', () => {
200+
cy.get('[data-btn=default]')
201+
.click()
202+
.document()
203+
.then((doc) => {
204+
cy.stub(doc, 'hidden').value(true)
205+
})
206+
.document()
207+
.trigger('visibilitychange')
208+
.get('._toastBar')
209+
.then(($bar) => {
210+
const old = parseFloat($bar.val())
211+
cy.wait(500).then(() => {
212+
expect(parseFloat($bar.val())).to.be.equal(old)
213+
})
214+
})
215+
.document()
216+
.then((doc) => {
217+
cy.stub(doc, 'hidden').value(false)
218+
})
219+
.document()
220+
.trigger('visibilitychange')
221+
.get('._toastBar')
222+
.then(($bar) => {
223+
const old = parseFloat($bar.val())
224+
cy.wait(500).then(() => {
225+
expect(parseFloat($bar.val())).to.be.below(old)
226+
})
227+
})
228+
.get('._toastBtn')
229+
.click()
230+
})
231+
*/
232+
233+
// Backward compatibility tests
234+
235+
test('`progress` key still works', async ({ page }) => {
236+
const get = async () => parseFloat(await page.locator('._toastBar').getAttribute('value'))
237+
await page.goto('/', { waitUntil: 'networkidle' })
238+
const id = await page.evaluate(`window.toast.push('test',{duration:1,initial:0,progress:0})`)
239+
expect(await get()).toBe(0)
240+
await page.evaluate(`window.toast.set(${id},{progress:0.2})`)
241+
await sleep(50)
242+
expect(await get()).toBe(0.2)
243+
})
244+
245+
test('`push()` accepts both string and obj', async ({ page }) => {
246+
await page.goto('/', { waitUntil: 'networkidle' })
247+
await page.evaluate(`window.toast.push('push with string')`)
248+
await expect(page.getByText('push with string')).toBeVisible()
249+
await page.evaluate(`window.toast.push({msg:'push with obj'})`)
250+
await expect(page.getByText('push with obj')).toBeVisible()
251+
})

0 commit comments

Comments
 (0)