UNPKG

@v4fire/client

Version:

V4Fire client core library

506 lines (385 loc) • 11.9 kB
/* eslint-disable max-lines-per-function */ // @ts-check /*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * @typedef {import('playwright').Page} Page */ const h = include('tests/helpers').default, delay = require('delay'), images = include('src/core/dom/image/test/const'); /** * Starts a test * * @param {Page} page * @param {!Object} params * @returns {!Promise<void>} */ module.exports = async (page, params) => { await h.utils.setup(page, params.context); let component, node, isClosed; page.on('close', () => isClosed = true); const mainImg = () => page.$('.b-image__img'), getRandomUrlPostfix = () => `${Math.random().toString().substr(10)}x${Math.random().toString().substr(10)}`, getRandomImgUrl = () => `https://fakeim.pl/${getRandomUrlPostfix()}`, abortImageRequest = (url, sleep = 0) => handleImageRequest(url, sleep, ''), getSrc = (node) => node.evaluate((el) => globalThis.getSrc(el)); beforeEach(async () => { await page.evaluate(() => { globalThis.removeCreatedComponents(); globalThis.getSrc = (ctx) => { if (ctx instanceof HTMLImageElement) { return ctx.currentSrc; } return ctx.style.backgroundImage.match(/url\("(.*)"\)/)?.[1] ?? ''; }; }); }); describe('b-image', () => { describe('src', () => { it('renders a component with the provided `src`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); await init({ src: imgUrl }); await reqPromise; await h.bom.waitForIdleCallback(page, {sleepAfterIdles: 200}); expect(await getSrc(await mainImg())).toBe(imgUrl); }); it('re-renders a component with a new `src`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); await init({ src: imgUrl }); await reqPromise; await h.bom.waitForIdleCallback(page); const newImgUrl = getRandomImgUrl(), newReqPromise = handleImageRequest(newImgUrl); await component.evaluate((ctx, [newImgUrl]) => ctx.src = newImgUrl, [newImgUrl]); await newReqPromise; await h.bom.waitForIdleCallback(page); expect(await getSrc(await mainImg())).toBe(newImgUrl); }); }); describe('srcset', () => { it('renders a component with the provided `srcset`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); await init({ srcset: {'1x': imgUrl} }); await reqPromise; await h.bom.waitForIdleCallback(page); expect(await getSrc(await mainImg())).toBe(imgUrl); }); }); describe('alt', () => { it('renders a component with aria attributes', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const altText = 'alt text'; await init({ src: imgUrl, alt: altText }); await reqPromise; await h.bom.waitForIdleCallback(page); const attrsVal = await node.evaluate((ctx) => [ ctx.getAttribute('role'), ctx.getAttribute('aria-label') ]); expect(attrsVal[0]).toBe('img'); expect(attrsVal[1]).toBe(altText); }); }); describe('position', () => { it('renders a component with the provided `position`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const position = '40% 40%'; await init({ src: imgUrl, position }); await reqPromise; await h.bom.waitForIdleCallback(page); expect(await (await mainImg()).evaluate((ctx) => ctx.style.backgroundPosition)).toBe(position); }); it('by default is set to 50% 50%', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const position = '50% 50%'; await init({ src: imgUrl }); await reqPromise; await h.bom.waitForIdleCallback(page); expect(await (await mainImg()).evaluate((ctx) => ctx.style.backgroundPosition)).toBe(position); }); }); describe('ratio', () => { it('renders a component with the provided `ratio`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const ratio = 100 / 50, expected = `${(1 / (100 / 50)) * 100}%`; await init({ src: imgUrl, ratio }); await reqPromise; await h.bom.waitForIdleCallback(page); expect(await (await mainImg()).evaluate((ctx) => ctx.style.paddingBottom)).toBe(expected); }); }); describe('beforeImg, afterImg', () => { it('renders a component with the provided `beforeImg`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const beforeImg = 'linear-gradient(rgb(230, 100, 101), rgb(145, 152, 229))'; await init({ src: imgUrl, beforeImg }); await reqPromise; await h.bom.waitForIdleCallback(page); const bg = await (await mainImg()).evaluate((ctx) => ctx.style.backgroundImage); expect(bg.startsWith(beforeImg)).toBe(true); }); it('renders a component with the provided `afterImg`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const afterImg = 'linear-gradient(rgb(230, 97, 101), rgb(145, 40, 229))'; await init({ src: imgUrl, afterImg }); await reqPromise; await h.bom.waitForIdleCallback(page); const bg = await (await mainImg()).evaluate((ctx) => ctx.style.backgroundImage); expect(bg.endsWith(afterImg)).toBe(true); }); it('renders a component with the provided `afterImg` and `beforeImg`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl); const beforeImg = 'linear-gradient(rgb(230, 100, 101), rgb(145, 152, 229))', afterImg = 'linear-gradient(rgb(230, 97, 101), rgb(145, 40, 229))'; await init({ src: imgUrl, beforeImg, afterImg }); await reqPromise; await h.bom.waitForIdleCallback(page); const bg = await (await mainImg()).evaluate((ctx) => ctx.style.backgroundImage); expect(bg.startsWith(beforeImg)).toBe(true); expect(bg.endsWith(afterImg)).toBe(true); }); }); describe('overlayImg', () => { it('renders a component with the overlay image if loading is still in progress', async () => { const imgUrl = getRandomImgUrl(), previewImg = images.preview, reqPromise = handleImageRequest(imgUrl, 1000); await init({ src: imgUrl, overlayImg: previewImg }); await h.bom.waitForIdleCallback(page); const previewElSelector = await component.evaluate( (ctx) => ctx.block.getElSelector('overlay-img') ); const previewSrc = await component.evaluate( (ctx) => globalThis.getSrc(ctx.block.element('overlay-img')) ); await expectAsync(page.waitForSelector(previewElSelector, {state: 'visible'})).toBeResolved(); expect(previewSrc).toBe(previewImg); await reqPromise; }); it('hides an overlay image if loading is complete', async () => { const imgUrl = getRandomImgUrl(), previewImg = images.preview, reqPromise = handleImageRequest(imgUrl, 300); await init({ src: imgUrl, overlayImg: previewImg }); await h.bom.waitForIdleCallback(page); const opacity = await component.evaluate((ctx) => ctx.block.element('overlay-img').style.opacity); await reqPromise; expect(Number(opacity)).toBe(0); }); it('hides an overlay image if loading is failed', async () => { const imgUrl = getRandomImgUrl(), previewImg = images.preview, reqPromise = abortImageRequest(imgUrl, 300); await init({ src: imgUrl, overlayImg: previewImg }); await h.bom.waitForIdleCallback(page); const opacity = await component.evaluate((ctx) => ctx.block.element('overlay-img').style.opacity); await reqPromise; await h.bom.waitForIdleCallback(page); expect(Number(opacity)).toBe(0); }); }); describe('brokenImg', () => { it('renders a component with the broken image if loading error occurs', async () => { const imgUrl = getRandomImgUrl(), brokenImg = images.broken, reqPromise = abortImageRequest(imgUrl, 100); await init({ src: imgUrl, brokenImg }); await h.bom.waitForIdleCallback(page); const brokenElSelector = await component.evaluate( (ctx) => ctx.block.getElSelector('broken-img') ); const brokenElSrc = await component.evaluate( (ctx) => globalThis.getSrc(ctx.block.element('broken-img')) ); await reqPromise; await h.bom.waitForIdleCallback(page); await expectAsync(page.waitForSelector(brokenElSelector, {state: 'visible'})).toBeResolved(); expect(brokenElSrc).toBe(brokenImg); }); it('renders a component without the broken image if loading was successful', async () => { const imgUrl = getRandomImgUrl(), brokenImg = images.broken, reqPromise = abortImageRequest(imgUrl, 300); await init({ src: imgUrl, brokenImg }); await h.bom.waitForIdleCallback(page); const opacity = await component.evaluate((ctx) => ctx.block.element('broken-img').style.opacity); await reqPromise; await h.bom.waitForIdleCallback(page); expect(Number(opacity)).toBe(0); }); }); describe('image loading failed', () => { it('fires the `loadFail` event', async () => { const imgUrl = getRandomImgUrl(), reqPromise = abortImageRequest(imgUrl, 300); await init({ src: imgUrl }); const eventPromise = component.evaluate((ctx) => new Promise((res) => ctx.once('loadFail', res))); await reqPromise; await expectAsync(eventPromise).toBeResolved(); }); it('sets the `showError` mod to `true`', async () => { const imgUrl = getRandomImgUrl(), reqPromise = abortImageRequest(imgUrl, 500); await init({ src: imgUrl }); await reqPromise; await h.bom.waitForIdleCallback(page); await expectAsync(page.waitForFunction(() => { const // @ts-ignore {component} = document.querySelector('#target'); return component.mods.showError === 'true'; })).toBeResolved(); }); }); describe('image loaded successfully', () => { it('fires the `loadSuccess` event', async () => { const imgUrl = getRandomImgUrl(), reqPromise = handleImageRequest(imgUrl, 100); await init({ src: imgUrl }); const eventPromise = component.evaluate((ctx) => new Promise((res) => ctx.once('loadSuccess', res))); await reqPromise; await h.bom.waitForIdleCallback(page); await expectAsync(eventPromise).toBeResolved(); }); }); }); function handleImageRequest(url, sleep = 0, base64Img = images.pngImage) { return page.route(url, async (route) => { await delay(sleep); if (isClosed) { return; } if (base64Img === '') { await route.abort('failed'); return; } const res = base64Img.split(',')[1], headers = route.request().headers(); headers['Content-Length'] = String(res?.length ?? 0); await route.fulfill({ status: 200, body: Buffer.from(res, 'base64'), contentType: 'image/png', headers }); }); } async function init(props = {}) { await page.evaluate((props) => { const scheme = [ { attrs: { id: 'target', ...props } } ]; globalThis.renderComponents('b-image', scheme); }, props); node = await h.dom.waitForEl(page, '#target'); component = await h.component.waitForComponent(page, '#target'); } };