Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions documents/src/pages/elements/icon.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,6 @@ The size and color of an icon can be changed using standard CSS styling.
<ef-icon class="large" icon="flag-2"></ef-icon>
```

## Icon preloading
`ef-icon` has a helper function to preload a set of icons. Icons can be loaded faster if you have a known set of icons for use in the app.

Preloading icons will be deferred until the first `ef-icon` component is created.

```javascript
import { preload } from '@refinitiv-ui/elements/icon';

// preload function supports both icon name or svg location, either single icon or multiple.
preload('eye');
preload('https://cdn.io/eye.svg');
preload('eye', 'heart', 'like', 'arrow-up');
preload(
'https://cdn.io/eye.svg',
'https://cdn.io/heart.svg',
'https://cdn.io/like.svg',
'https://cdn.io/arrow-up.svg'
);
```

## Accessibility
::a11y-intro::

Expand Down
2 changes: 1 addition & 1 deletion packages/elemental-theme/src/custom-elements/ef-icon.less
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
:host {
--cdn-prefix: 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/icons/';
--cdn-sprite-prefix: 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/sprites/icons.svg';

// to make :active work on IE11
svg {
Expand Down
3 changes: 0 additions & 3 deletions packages/elements/src/collapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@ import { state } from '@refinitiv-ui/core/decorators/state.js';
import { Ref, createRef, ref } from '@refinitiv-ui/core/directives/ref.js';

import '../header/index.js';
import { preload } from '../icon/index.js';
import '../icon/index.js';
import '../panel/index.js';
import type { Panel } from '../panel/index.js';
import { VERSION } from '../version.js';

preload('right'); /* preload calendar icons for faster loading */

/**
* Allows users to hide non-critical information
* or areas of the screen, maximizing the amount of real estate
Expand Down
3 changes: 0 additions & 3 deletions packages/elements/src/datetime-picker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import '../calendar/index.js';
import type { OpenedChangedEvent, ValueChangedEvent, ViewChangedEvent } from '../events';
import type { Icon } from '../icon';
import '../icon/index.js';
import { preload } from '../icon/index.js';
import type { Overlay } from '../overlay';
import '../overlay/index.js';
import type { TextField } from '../text-field';
Expand All @@ -65,8 +64,6 @@ import { getDateFNSLocale } from './locales.js';
import type { DatetimePickerDuplex, DatetimePickerFilter } from './types';
import { DateTimeSegment, formatToView, getCurrentTime } from './utils.js';

preload('calendar', 'down', 'left', 'right'); /* preload calendar icons for faster loading */

export type { DatetimePickerDuplex, DatetimePickerFilter };

/**
Expand Down
153 changes: 83 additions & 70 deletions packages/elements/src/flag/__test__/flag.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@ import '@refinitiv-ui/elements/flag';
import { preload } from '@refinitiv-ui/elements/flag';

import '@refinitiv-ui/halo-theme/light/ef-flag.js';
import { elementUpdated, expect, isIE } from '@refinitiv-ui/test-helpers';
import { elementUpdated, expect } from '@refinitiv-ui/test-helpers';

import {
checkRequestedUrl,
createAndWaitForLoad,
createFakeResponse,
createMockSrc,
flagName,
gbSvg,
generateUniqueName
generateUniqueName,
isEqualSvg,
responseConfigError,
responseConfigSuccess
} from './helpers/helpers.js';

describe('flag/Flag', function () {
let fetch;
beforeEach(function () {
fetch = sinon.stub(window, 'fetch');
});
afterEach(function () {
window.fetch.restore(); // remove stub
});
describe('Should Have Correct DOM Structure', function () {
it('without flag or src attributes', async function () {
const el = await createAndWaitForLoad('<ef-flag></ef-flag>');
Expand All @@ -25,42 +36,34 @@ describe('flag/Flag', function () {
});

it('with valid flag attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
createFakeResponse(gbSvg, responseConfigSuccess);
const el = await createAndWaitForLoad(`<ef-flag flag="${flagName}"></ef-flag>`);
const svg = el.shadowRoot.querySelector('svg');
expect(svg).to.not.equal(null, 'SVG element should exist for valid flag attribute');
// Unable to make snapshots of SVGs because of semantic-dom-dif: https://open-wc.org/testing/semantic-dom-diff.html
// Avoiding this check on IE because it adds custom attributes which cant be ignored with `ignoreAttributes`
if (!isIE) {
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
}
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
});

it('with valid src attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
createFakeResponse(gbSvg, responseConfigSuccess);
const el = await createAndWaitForLoad('<ef-flag src="https://mock.cdn.com/flags/ticks.svg"></ef-flag>');
const svg = el.shadowRoot.querySelector('svg');

expect(svg).to.not.equal(null, 'SVG element should exist for valid src attribute');
if (!isIE) {
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
}
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
});

it('with invalid flag attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([404, {}, '']);
createFakeResponse('', responseConfigError);
const el = await createAndWaitForLoad('<ef-flag flag="invalid"></ef-flag>');
const svg = el.shadowRoot.querySelector('svg');

expect(svg).to.equal(null, 'SVG element should not exist for invalid flag attribute');
});

it('with invalid src attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([404, {}, '']);
createFakeResponse('', responseConfigError);
const el = await createAndWaitForLoad(
'<ef-flag src="https://mock.cdn.com/flags/invalid.svg"></ef-flag>'
);
Expand All @@ -70,26 +73,38 @@ describe('flag/Flag', function () {
});

it('with empty flag attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([404, {}, '']);
createFakeResponse('', responseConfigError);
const el = await createAndWaitForLoad('<ef-flag flag=""></ef-flag>');
const svg = el.shadowRoot.querySelector('svg');

expect(svg).to.equal(null, 'SVG element should not exist for empty flag attribute');
});

it('with empty src attribute', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([404, {}, '']);
createFakeResponse('', responseConfigError);
const el = await createAndWaitForLoad('<ef-flag src=""></ef-flag>');
const svg = el.shadowRoot.querySelector('svg');

expect(svg).to.equal(null, 'SVG element should not exist for empty src attribute');
});

it('With valid flag attribute to invalid one', async function () {
createFakeResponse(gbSvg, responseConfigSuccess);
const el = await createAndWaitForLoad(`<ef-flag flag="${flagName}"></ef-flag>`);
let svg = el.shadowRoot.querySelector('svg');

expect(svg).to.not.equal(null, 'SVG element should exist for valid flag attribute');
expect(isEqualSvg(svg.outerHTML, gbSvg)).to.equal(true, 'Should render SVG, from the server response');

el.setAttribute('flag', 'invalid');
await elementUpdated(el);
svg = el.shadowRoot.querySelector('svg');

expect(svg).to.equal(null, 'SVG element should not exist for invalid flag attribute');
});

it('with unsafe nodes in response', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, '<script></script>']);
createFakeResponse('<script></script>', responseConfigSuccess);
const el = await createAndWaitForLoad('<ef-flag flag="malicious"></ef-flag>');
const script = el.shadowRoot.querySelector('script');

Expand All @@ -99,24 +114,32 @@ describe('flag/Flag', function () {

describe('Should have correct attributes and properties', function () {
describe('flag attribute/property', function () {
let server;
// let server;
let el;
// let fetch;

before(function () {
server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
});
// before(function () {
// server = sinon.createFakeServer({ respondImmediately: true });
// server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
// });

beforeEach(async function () {
before(async function () {
el = await createAndWaitForLoad('<ef-flag></ef-flag>');
// fetch = sinon.stub(window, 'fetch');
// createFakeResponse(gbSvg, responseConfigSuccess);
});
// afterEach(function () {
// window.fetch.restore(); // remove stub
// });

it('should not have flag attribute by default', function () {
createFakeResponse(gbSvg, responseConfigSuccess);
expect(el.hasAttribute('flag')).to.equal(false, 'Flag should not have the flag attribute by default');
expect(el.flag).to.equal(null, 'Flag should not have the flag property by default');
});

it('should have flag attribute when set', async function () {
createFakeResponse(gbSvg, responseConfigSuccess);
el.setAttribute('flag', flagName);
await elementUpdated(el);

Expand All @@ -129,6 +152,7 @@ describe('flag/Flag', function () {
});

it('should have flag attribute reflected when flag property is set', async function () {
createFakeResponse(gbSvg, responseConfigSuccess);
el.flag = flagName;
await elementUpdated(el);

Expand All @@ -142,17 +166,12 @@ describe('flag/Flag', function () {

describe('src attribute/property', function () {
let srcValue;
let server;
let el;

before(function () {
srcValue = createMockSrc(flagName);
server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
});

beforeEach(async function () {
srcValue = createMockSrc(flagName);
el = await createAndWaitForLoad('<ef-flag></ef-flag>');
createFakeResponse(gbSvg, responseConfigSuccess);
});

it('should not have src attribute by default', function () {
Expand Down Expand Up @@ -187,8 +206,6 @@ describe('flag/Flag', function () {

describe('Functional Tests', function () {
it('should set the src property based on the flag and CDN prefix', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
const el = await createAndWaitForLoad(`<ef-flag flag="${flagName}"></ef-flag>`);
const CDNPrefix = el.getComputedVariable('--cdn-prefix');

Expand All @@ -207,42 +224,38 @@ describe('flag/Flag', function () {
});

it('should make a correct server request based on cdn prefix and the flag if flag is specified', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
createFakeResponse(gbSvg, responseConfigSuccess);
const uniqueFlagName = generateUniqueName(flagName); // to avoid caching
const el = await createAndWaitForLoad(`<ef-flag flag="${uniqueFlagName}"></ef-flag>`);
const CDNPrefix = el.getComputedVariable('--cdn-prefix');

expect(CDNPrefix, 'CDN prefix should exist to create the src based on the flag').to.exist;
const expectedSrc = `${CDNPrefix}${uniqueFlagName}.svg`;

expect(server.requests.length).to.equal(1, 'Should make one request');
expect(server.requests[0].url).to.equal(
expectedSrc,
`requested URL should be ${expectedSrc} for the flag ${uniqueFlagName}`
expect(fetch.callCount).to.equal(1, 'Should make one request');
expect(checkRequestedUrl(fetch.args, expectedSrc)).to.equal(
true,
`Requested URL should be ${expectedSrc} for the flag ${uniqueFlagName}`
);
});

it('should make a correct server request based on src', async function () {
const server = sinon.createFakeServer({ respondImmediately: true });
createFakeResponse(gbSvg, responseConfigSuccess);
const uniqueFlagName = generateUniqueName(flagName); // to avoid caching
const uniqueSrc = createMockSrc(uniqueFlagName);
server.respondWith('GET', uniqueSrc, [200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);

await createAndWaitForLoad(`<ef-flag src="${uniqueSrc}"></ef-flag>`);
expect(server.requests.length).to.equal(1, 'Should make one request');
expect(server.requests[0].url).to.equal(uniqueSrc, `requested URL should be ${uniqueSrc}`);
expect(fetch.callCount).to.equal(1, 'Should make one request');
expect(checkRequestedUrl(fetch.args, uniqueSrc)).to.equal(true, `Requested URL should be ${uniqueSrc}`);
});

it('should preload flags', async function () {
let server = sinon.createFakeServer({ respondImmediately: true });

const el = await createAndWaitForLoad('<ef-flag></ef-flag>');
const CDNPrefix = el.getComputedVariable('--cdn-prefix');

expect(CDNPrefix, 'CDN prefix should exist in order for preload to work properly with flag name').to
.exist;
expect(server.requests.length).to.equal(0, 'No request should be sent for empty flag');
expect(fetch.callCount).to.equal(0, 'No request should be sent for empty flag');

const firstUniqueFlag = generateUniqueName(flagName);
const secondUniqueFlag = generateUniqueName(flagName);
Expand All @@ -252,26 +265,29 @@ describe('flag/Flag', function () {
const secondUniqueFlagSrc = createMockSrc(secondUniqueFlag);
const uniqueInvalidFlagSrc = `${CDNPrefix}${uniqueInvalidFlag}.svg`;

server.respondWith('GET', firstUniqueFlagSrc, [200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
server.respondWith('GET', secondUniqueFlagSrc, [200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
server.respondWith('GET', uniqueInvalidFlagSrc, [404, {}, '']);

const preloadedFlags = await Promise.all(
preload(firstUniqueFlag, secondUniqueFlagSrc, uniqueInvalidFlag)
fetch.withArgs(firstUniqueFlagSrc).resolves(
new Response(gbSvg, {
status: 200,
headers: { 'Content-Type': 'image/svg+xml' }
})
);
expect(server.requests.length).to.equal(3, 'Server requests for all preloaded flags should be made');
expect(checkRequestedUrl(server.requests, firstUniqueFlagSrc)).to.equal(
true,
'should request flags by name with CDN prefix'
fetch.withArgs(secondUniqueFlagSrc).resolves(
new Response(gbSvg, {
status: 200,
headers: { 'Content-Type': 'image/svg+xml' }
})
);
expect(checkRequestedUrl(server.requests, secondUniqueFlagSrc)).to.equal(
true,
'should request flags with src'
fetch.withArgs(uniqueInvalidFlagSrc).resolves(
new Response('', {
status: 404,
headers: {}
})
);
expect(checkRequestedUrl(server.requests, uniqueInvalidFlagSrc)).to.equal(
true,
'should try to request invalid flag'

const preloadedFlags = await Promise.all(
preload(firstUniqueFlag, secondUniqueFlagSrc, uniqueInvalidFlag)
);
expect(fetch.callCount).to.equal(3, 'Server requests for all preloaded flags should be made');
expect(preloadedFlags[0].length > 0).to.equal(
true,
'Should successfully preload flag by name with CDN prefix'
Expand All @@ -281,10 +297,7 @@ describe('flag/Flag', function () {
el.setAttribute('flag', firstUniqueFlag);
await elementUpdated(el);

expect(server.requests.length).to.equal(
3,
'no new requests should be made since flags are already preloaded'
);
expect(fetch.callCount).to.equal(3, 'no new requests should be made since flags are already preloaded');
});
});
});
Loading
Loading