Skip to content

Commit be7f5d7

Browse files
feat(icon): add icon sprites (#1274)
1 parent 472b27b commit be7f5d7

File tree

26 files changed

+652
-335
lines changed

26 files changed

+652
-335
lines changed

documents/src/pages/elements/icon.md

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,6 @@ The size and color of an icon can be changed using standard CSS styling.
6464
<ef-icon class="large" icon="flag-2"></ef-icon>
6565
```
6666
67-
## Icon preloading
68-
`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.
69-
70-
Preloading icons will be deferred until the first `ef-icon` component is created.
71-
72-
```javascript
73-
import { preload } from '@refinitiv-ui/elements/icon';
74-
75-
// preload function supports both icon name or svg location, either single icon or multiple.
76-
preload('eye');
77-
preload('https://cdn.io/eye.svg');
78-
preload('eye', 'heart', 'like', 'arrow-up');
79-
preload(
80-
'https://cdn.io/eye.svg',
81-
'https://cdn.io/heart.svg',
82-
'https://cdn.io/like.svg',
83-
'https://cdn.io/arrow-up.svg'
84-
);
85-
```
86-
8767
## Accessibility
8868
::a11y-intro::
8969

packages/elemental-theme/src/custom-elements/ef-icon.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
:host {
2-
--cdn-prefix: 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/icons/';
2+
--cdn-sprite-prefix: 'https://cdn.refinitiv.net/public/libs/elf/assets/elf-theme-halo/resources/sprites/icons.svg';
33

44
// to make :active work on IE11
55
svg {

packages/elements/src/collapse/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ import { state } from '@refinitiv-ui/core/decorators/state.js';
1414
import { Ref, createRef, ref } from '@refinitiv-ui/core/directives/ref.js';
1515

1616
import '../header/index.js';
17-
import { preload } from '../icon/index.js';
1817
import '../icon/index.js';
1918
import '../panel/index.js';
2019
import type { Panel } from '../panel/index.js';
2120
import { VERSION } from '../version.js';
2221

23-
preload('right'); /* preload calendar icons for faster loading */
24-
2522
/**
2623
* Allows users to hide non-critical information
2724
* or areas of the screen, maximizing the amount of real estate

packages/elements/src/datetime-picker/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import '../calendar/index.js';
3939
import type { OpenedChangedEvent, ValueChangedEvent, ViewChangedEvent } from '../events';
4040
import type { Icon } from '../icon';
4141
import '../icon/index.js';
42-
import { preload } from '../icon/index.js';
4342
import type { Overlay } from '../overlay';
4443
import '../overlay/index.js';
4544
import type { TextField } from '../text-field';
@@ -65,8 +64,6 @@ import { getDateFNSLocale } from './locales.js';
6564
import type { DatetimePickerDuplex, DatetimePickerFilter } from './types';
6665
import { DateTimeSegment, formatToView, getCurrentTime } from './utils.js';
6766

68-
preload('calendar', 'down', 'left', 'right'); /* preload calendar icons for faster loading */
69-
7067
export type { DatetimePickerDuplex, DatetimePickerFilter };
7168

7269
/**

packages/elements/src/flag/__test__/flag.test.js

Lines changed: 83 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,29 @@ import '@refinitiv-ui/elements/flag';
55
import { preload } from '@refinitiv-ui/elements/flag';
66

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

1010
import {
1111
checkRequestedUrl,
1212
createAndWaitForLoad,
13+
createFakeResponse,
1314
createMockSrc,
1415
flagName,
1516
gbSvg,
16-
generateUniqueName
17+
generateUniqueName,
18+
isEqualSvg,
19+
responseConfigError,
20+
responseConfigSuccess
1721
} from './helpers/helpers.js';
1822

1923
describe('flag/Flag', function () {
24+
let fetch;
25+
beforeEach(function () {
26+
fetch = sinon.stub(window, 'fetch');
27+
});
28+
afterEach(function () {
29+
window.fetch.restore(); // remove stub
30+
});
2031
describe('Should Have Correct DOM Structure', function () {
2132
it('without flag or src attributes', async function () {
2233
const el = await createAndWaitForLoad('<ef-flag></ef-flag>');
@@ -25,42 +36,34 @@ describe('flag/Flag', function () {
2536
});
2637

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

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

4653
expect(svg).to.not.equal(null, 'SVG element should exist for valid src attribute');
47-
if (!isIE) {
48-
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
49-
}
54+
expect(svg.outerHTML).to.equal(gbSvg, 'Should render SVG, from the server response');
5055
});
5156

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

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

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

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

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

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

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

91+
it('With valid flag attribute to invalid one', async function () {
92+
createFakeResponse(gbSvg, responseConfigSuccess);
93+
const el = await createAndWaitForLoad(`<ef-flag flag="${flagName}"></ef-flag>`);
94+
let svg = el.shadowRoot.querySelector('svg');
95+
96+
expect(svg).to.not.equal(null, 'SVG element should exist for valid flag attribute');
97+
expect(isEqualSvg(svg.outerHTML, gbSvg)).to.equal(true, 'Should render SVG, from the server response');
98+
99+
el.setAttribute('flag', 'invalid');
100+
await elementUpdated(el);
101+
svg = el.shadowRoot.querySelector('svg');
102+
103+
expect(svg).to.equal(null, 'SVG element should not exist for invalid flag attribute');
104+
});
105+
90106
it('with unsafe nodes in response', async function () {
91-
const server = sinon.createFakeServer({ respondImmediately: true });
92-
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, '<script></script>']);
107+
createFakeResponse('<script></script>', responseConfigSuccess);
93108
const el = await createAndWaitForLoad('<ef-flag flag="malicious"></ef-flag>');
94109
const script = el.shadowRoot.querySelector('script');
95110

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

100115
describe('Should have correct attributes and properties', function () {
101116
describe('flag attribute/property', function () {
102-
let server;
117+
// let server;
103118
let el;
119+
// let fetch;
104120

105-
before(function () {
106-
server = sinon.createFakeServer({ respondImmediately: true });
107-
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
108-
});
121+
// before(function () {
122+
// server = sinon.createFakeServer({ respondImmediately: true });
123+
// server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
124+
// });
109125

110-
beforeEach(async function () {
126+
before(async function () {
111127
el = await createAndWaitForLoad('<ef-flag></ef-flag>');
128+
// fetch = sinon.stub(window, 'fetch');
129+
// createFakeResponse(gbSvg, responseConfigSuccess);
112130
});
131+
// afterEach(function () {
132+
// window.fetch.restore(); // remove stub
133+
// });
113134

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

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

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

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

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

143167
describe('src attribute/property', function () {
144168
let srcValue;
145-
let server;
146169
let el;
147170

148-
before(function () {
149-
srcValue = createMockSrc(flagName);
150-
server = sinon.createFakeServer({ respondImmediately: true });
151-
server.respondWith([200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
152-
});
153-
154171
beforeEach(async function () {
172+
srcValue = createMockSrc(flagName);
155173
el = await createAndWaitForLoad('<ef-flag></ef-flag>');
174+
createFakeResponse(gbSvg, responseConfigSuccess);
156175
});
157176

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

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

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

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

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

219-
expect(server.requests.length).to.equal(1, 'Should make one request');
220-
expect(server.requests[0].url).to.equal(
221-
expectedSrc,
222-
`requested URL should be ${expectedSrc} for the flag ${uniqueFlagName}`
235+
expect(fetch.callCount).to.equal(1, 'Should make one request');
236+
expect(checkRequestedUrl(fetch.args, expectedSrc)).to.equal(
237+
true,
238+
`Requested URL should be ${expectedSrc} for the flag ${uniqueFlagName}`
223239
);
224240
});
225241

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

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

237252
it('should preload flags', async function () {
238-
let server = sinon.createFakeServer({ respondImmediately: true });
239-
240253
const el = await createAndWaitForLoad('<ef-flag></ef-flag>');
241254
const CDNPrefix = el.getComputedVariable('--cdn-prefix');
242255

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

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

255-
server.respondWith('GET', firstUniqueFlagSrc, [200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
256-
server.respondWith('GET', secondUniqueFlagSrc, [200, { 'Content-Type': 'image/svg+xml' }, gbSvg]);
257-
server.respondWith('GET', uniqueInvalidFlagSrc, [404, {}, '']);
258-
259-
const preloadedFlags = await Promise.all(
260-
preload(firstUniqueFlag, secondUniqueFlagSrc, uniqueInvalidFlag)
268+
fetch.withArgs(firstUniqueFlagSrc).resolves(
269+
new Response(gbSvg, {
270+
status: 200,
271+
headers: { 'Content-Type': 'image/svg+xml' }
272+
})
261273
);
262-
expect(server.requests.length).to.equal(3, 'Server requests for all preloaded flags should be made');
263-
expect(checkRequestedUrl(server.requests, firstUniqueFlagSrc)).to.equal(
264-
true,
265-
'should request flags by name with CDN prefix'
274+
fetch.withArgs(secondUniqueFlagSrc).resolves(
275+
new Response(gbSvg, {
276+
status: 200,
277+
headers: { 'Content-Type': 'image/svg+xml' }
278+
})
266279
);
267-
expect(checkRequestedUrl(server.requests, secondUniqueFlagSrc)).to.equal(
268-
true,
269-
'should request flags with src'
280+
fetch.withArgs(uniqueInvalidFlagSrc).resolves(
281+
new Response('', {
282+
status: 404,
283+
headers: {}
284+
})
270285
);
271-
expect(checkRequestedUrl(server.requests, uniqueInvalidFlagSrc)).to.equal(
272-
true,
273-
'should try to request invalid flag'
286+
287+
const preloadedFlags = await Promise.all(
288+
preload(firstUniqueFlag, secondUniqueFlagSrc, uniqueInvalidFlag)
274289
);
290+
expect(fetch.callCount).to.equal(3, 'Server requests for all preloaded flags should be made');
275291
expect(preloadedFlags[0].length > 0).to.equal(
276292
true,
277293
'Should successfully preload flag by name with CDN prefix'
@@ -281,10 +297,7 @@ describe('flag/Flag', function () {
281297
el.setAttribute('flag', firstUniqueFlag);
282298
await elementUpdated(el);
283299

284-
expect(server.requests.length).to.equal(
285-
3,
286-
'no new requests should be made since flags are already preloaded'
287-
);
300+
expect(fetch.callCount).to.equal(3, 'no new requests should be made since flags are already preloaded');
288301
});
289302
});
290303
});

0 commit comments

Comments
 (0)