summaryrefslogtreecommitdiff
path: root/packages/astro/test/units/assets/fonts/implementations.test.js
diff options
context:
space:
mode:
Diffstat (limited to 'packages/astro/test/units/assets/fonts/implementations.test.js')
-rw-r--r--packages/astro/test/units/assets/fonts/implementations.test.js291
1 files changed, 291 insertions, 0 deletions
diff --git a/packages/astro/test/units/assets/fonts/implementations.test.js b/packages/astro/test/units/assets/fonts/implementations.test.js
new file mode 100644
index 000000000..b5f2d5a7c
--- /dev/null
+++ b/packages/astro/test/units/assets/fonts/implementations.test.js
@@ -0,0 +1,291 @@
+// @ts-check
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+import {
+ handleValueWithSpaces,
+ renderCssVariable,
+ renderFontFace,
+ withFamily,
+} from '../../../../dist/assets/fonts/implementations/css-renderer.js';
+import { createDataCollector } from '../../../../dist/assets/fonts/implementations/data-collector.js';
+import { createAstroErrorHandler } from '../../../../dist/assets/fonts/implementations/error-handler.js';
+import { createCachedFontFetcher } from '../../../../dist/assets/fonts/implementations/font-fetcher.js';
+import { createFontTypeExtractor } from '../../../../dist/assets/fonts/implementations/font-type-extractor.js';
+import { createSpyStorage, simpleErrorHandler } from './utils.js';
+
+describe('fonts implementations', () => {
+ describe('createMinifiableCssRenderer()', () => {
+ describe('renderFontFace()', () => {
+ it('filters undefined properties properly', () => {
+ assert.equal(renderFontFace({ foo: 'test' }, true).includes('foo:test'), true);
+ assert.equal(renderFontFace({ foo: 'test', bar: undefined }, true).includes('bar'), false);
+ });
+
+ it('formats properly', () => {
+ assert.equal(renderFontFace({ foo: 'test' }, false), '@font-face {\n foo: test;\n}\n');
+ assert.equal(renderFontFace({ foo: 'test' }, true), '@font-face{foo:test;}');
+ });
+ });
+
+ it('renderCssVariable()', () => {
+ assert.equal(
+ renderCssVariable('foo', ['bar', 'x y'], false),
+ ':root {\n foo: bar, "x y";\n}\n',
+ );
+ assert.equal(renderCssVariable('foo', ['bar', 'x y'], true), ':root{foo:bar,"x y";}');
+ });
+
+ it('withFamily()', () => {
+ assert.deepStrictEqual(withFamily('foo', { bar: 'baz' }), {
+ 'font-family': 'foo',
+ bar: 'baz',
+ });
+ assert.deepStrictEqual(withFamily('x y', { bar: 'baz' }), {
+ 'font-family': '"x y"',
+ bar: 'baz',
+ });
+ });
+
+ it('handleValueWithSpaces()', () => {
+ assert.equal(handleValueWithSpaces('foo'), 'foo');
+ assert.equal(handleValueWithSpaces('x y'), '"x y"');
+ });
+ });
+
+ it('createDataCollector()', () => {
+ /** @type {Map<string, string>} */
+ const map = new Map();
+ /** @type {Array<import('../../../../dist/assets/fonts/types.js').PreloadData>} */
+ const preloadData = [];
+ /** @type {Array<import('../../../../dist/assets/fonts/logic/optimize-fallbacks.js').CollectedFontForMetrics>} */
+ const collectedFonts = [];
+
+ const dataCollector = createDataCollector({
+ hasUrl: (hash) => map.has(hash),
+ saveUrl: (hash, url) => {
+ map.set(hash, url);
+ },
+ savePreload: (preload) => {
+ preloadData.push(preload);
+ },
+ saveFontData: (collected) => {
+ collectedFonts.push(collected);
+ },
+ });
+
+ dataCollector.collect({ hash: 'xxx', originalUrl: 'abc', preload: null, data: {} });
+ dataCollector.collect({
+ hash: 'yyy',
+ originalUrl: 'def',
+ preload: { type: 'woff2', url: 'def' },
+ data: {},
+ });
+ dataCollector.collect({ hash: 'xxx', originalUrl: 'abc', preload: null, data: {} });
+
+ assert.deepStrictEqual(
+ [...map.entries()],
+ [
+ ['xxx', 'abc'],
+ ['yyy', 'def'],
+ ],
+ );
+ assert.deepStrictEqual(preloadData, [{ type: 'woff2', url: 'def' }]);
+ assert.deepStrictEqual(collectedFonts, [
+ { hash: 'xxx', url: 'abc', data: {} },
+ { hash: 'yyy', url: 'def', data: {} },
+ { hash: 'xxx', url: 'abc', data: {} },
+ ]);
+ });
+
+ it('createAstroErrorHandler()', () => {
+ const errorHandler = createAstroErrorHandler();
+ assert.equal(
+ errorHandler.handle({ type: 'cannot-extract-font-type', data: { url: '' }, cause: null })
+ .name,
+ 'CannotExtractFontType',
+ );
+ assert.equal(
+ errorHandler.handle({ type: 'cannot-fetch-font-file', data: { url: '' }, cause: null }).name,
+ 'CannotFetchFontFile',
+ );
+ assert.equal(
+ errorHandler.handle({
+ type: 'cannot-load-font-provider',
+ data: { entrypoint: '' },
+ cause: null,
+ }).name,
+ 'CannotLoadFontProvider',
+ );
+ assert.equal(
+ errorHandler.handle({ type: 'unknown-fs-error', data: {}, cause: null }).name,
+ 'UnknownFilesystemError',
+ );
+
+ assert.equal(
+ errorHandler.handle({
+ type: 'cannot-extract-font-type',
+ data: { url: '' },
+ cause: 'whatever',
+ }).cause,
+ 'whatever',
+ );
+ });
+
+ describe('createCachedFontFetcher()', () => {
+ /**
+ *
+ * @param {{ ok: boolean }} param0
+ */
+ function createReadFileMock({ ok }) {
+ /** @type {Array<string>} */
+ const filesUrls = [];
+ return {
+ filesUrls,
+ /** @type {(url: string) => Promise<Buffer>} */
+ readFile: async (url) => {
+ filesUrls.push(url);
+ if (!ok) {
+ throw 'fs error';
+ }
+ return Buffer.from('');
+ },
+ };
+ }
+
+ /**
+ *
+ * @param {{ ok: boolean }} param0
+ */
+ function createFetchMock({ ok }) {
+ /** @type {Array<string>} */
+ const fetchUrls = [];
+ return {
+ fetchUrls,
+ /** @type {(url: string) => Promise<Response>} */
+ fetch: async (url) => {
+ fetchUrls.push(url);
+ // @ts-expect-error
+ return {
+ ok,
+ status: ok ? 200 : 500,
+ arrayBuffer: async () => new ArrayBuffer(),
+ };
+ },
+ };
+ }
+
+ it('caches work', async () => {
+ const { filesUrls, readFile } = createReadFileMock({ ok: true });
+ const { fetchUrls, fetch } = createFetchMock({ ok: true });
+ const { storage, store } = createSpyStorage();
+ const fontFetcher = createCachedFontFetcher({
+ storage,
+ errorHandler: simpleErrorHandler,
+ readFile,
+ fetch,
+ });
+
+ await fontFetcher.fetch('abc', 'def');
+ await fontFetcher.fetch('foo', 'bar');
+ await fontFetcher.fetch('abc', 'def');
+
+ assert.deepStrictEqual([...store.keys()], ['abc', 'foo']);
+ assert.deepStrictEqual(filesUrls, []);
+ assert.deepStrictEqual(fetchUrls, ['def', 'bar']);
+ });
+
+ it('reads files if path is absolute', async () => {
+ const { filesUrls, readFile } = createReadFileMock({ ok: true });
+ const { fetchUrls, fetch } = createFetchMock({ ok: true });
+ const { storage } = createSpyStorage();
+ const fontFetcher = createCachedFontFetcher({
+ storage,
+ errorHandler: simpleErrorHandler,
+ readFile,
+ fetch,
+ });
+
+ await fontFetcher.fetch('abc', '/foo/bar');
+
+ assert.deepStrictEqual(filesUrls, ['/foo/bar']);
+ assert.deepStrictEqual(fetchUrls, []);
+ });
+
+ it('fetches files if path is not absolute', async () => {
+ const { filesUrls, readFile } = createReadFileMock({ ok: true });
+ const { fetchUrls, fetch } = createFetchMock({ ok: true });
+ const { storage } = createSpyStorage();
+ const fontFetcher = createCachedFontFetcher({
+ storage,
+ errorHandler: simpleErrorHandler,
+ readFile,
+ fetch,
+ });
+
+ await fontFetcher.fetch('abc', 'https://example.com');
+
+ assert.deepStrictEqual(filesUrls, []);
+ assert.deepStrictEqual(fetchUrls, ['https://example.com']);
+ });
+
+ it('throws the right error kind', async () => {
+ const { readFile } = createReadFileMock({ ok: false });
+ const { fetch } = createFetchMock({ ok: false });
+ const { storage } = createSpyStorage();
+ const fontFetcher = createCachedFontFetcher({
+ storage,
+ errorHandler: simpleErrorHandler,
+ readFile,
+ fetch,
+ });
+
+ let error = await fontFetcher.fetch('abc', '/foo/bar').catch((err) => err);
+ assert.equal(error instanceof Error, true);
+ assert.equal(error.message, 'cannot-fetch-font-file');
+ assert.equal(error.cause, 'fs error');
+
+ error = await fontFetcher.fetch('abc', 'https://example.com').catch((err) => err);
+ assert.equal(error instanceof Error, true);
+ assert.equal(error.message, 'cannot-fetch-font-file');
+ assert.equal(error.cause instanceof Error, true);
+ assert.equal(error.cause.message.includes('Response was not successful'), true);
+ });
+ });
+
+ // TODO: find a good way to test this
+ // describe('createCapsizeFontMetricsResolver()', () => {});
+
+ it('createFontTypeExtractor()', () => {
+ /** @type {Array<[string, false | string]>} */
+ const data = [
+ ['', false],
+ ['.', false],
+ ['test.', false],
+ ['https://foo.bar/file', false],
+ [
+ 'https://fonts.gstatic.com/s/roboto/v47/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaSTbQWt4N.woff2',
+ 'woff2',
+ ],
+ ['/home/documents/project/font.ttf', 'ttf'],
+ ];
+
+ const fontTypeExtractor = createFontTypeExtractor({ errorHandler: simpleErrorHandler });
+
+ for (const [input, check] of data) {
+ try {
+ const res = fontTypeExtractor.extract(input);
+ if (check) {
+ assert.equal(res, check);
+ } else {
+ assert.fail(`String ${JSON.stringify(input)} should not be valid`);
+ }
+ } catch (e) {
+ if (check) {
+ assert.fail(`String ${JSON.stringify(input)} should be valid`);
+ } else {
+ assert.equal(e instanceof Error, true);
+ }
+ }
+ }
+ });
+});