diff --git a/package.json b/package.json index 6bc022b..2a6358c 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,14 @@ "dev": "vitest", "lint": "eslint --ext .ts,.js .", "prepack": "unbuild", - "release": "pnpm test && pnpm build && changelogen --release && git push --follow-tags && pnpm publish", + "release": "pnpm test && pnpm build && changelogen --release --push && pnpm publish", "test": "pnpm lint && vitest run --coverage" }, "dependencies": { "deepmerge": "^4.3.1", "hookable": "^5.5.3", "ofetch": "^1.3.3", - "ufo": "^1.3.2" + "ufo": "^1.4.0" }, "devDependencies": { "@nuxtjs/eslint-config-typescript": "latest", @@ -44,5 +44,5 @@ "unbuild": "latest", "vitest": "latest" }, - "packageManager": "pnpm@8.11.0" -} \ No newline at end of file + "packageManager": "pnpm@8.15.4" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe9384d..85814b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ dependencies: specifier: ^1.3.3 version: 1.3.3 ufo: - specifier: ^1.3.2 - version: 1.3.2 + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@nuxtjs/eslint-config-typescript': @@ -3218,7 +3218,7 @@ packages: acorn: 8.11.2 pathe: 1.1.1 pkg-types: 1.0.3 - ufo: 1.3.2 + ufo: 1.4.0 dev: true /mri@1.2.0: @@ -3341,7 +3341,7 @@ packages: dependencies: destr: 2.0.2 node-fetch-native: 1.4.1 - ufo: 1.3.2 + ufo: 1.4.0 /ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} @@ -4425,8 +4425,8 @@ packages: hasBin: true dev: true - /ufo@1.3.2: - resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + /ufo@1.4.0: + resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} diff --git a/src/construct-url.ts b/src/construct-url.ts index 6c8573b..63cca12 100644 --- a/src/construct-url.ts +++ b/src/construct-url.ts @@ -3,9 +3,8 @@ import { GOOGLE_FONTS_DOMAIN, isValidDisplay, parseFamilyName, parseStyle } from import type { GoogleFonts, Families } from './types' export function constructURL ({ families, display, subsets, text }: GoogleFonts = {}): string | false { - const subset = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean) - const prefix = subset.length > 0 ? 'css' : 'css2' - const family = convertFamiliesToArray(families ?? {}, prefix.endsWith('2')) + const _subsets = (Array.isArray(subsets) ? subsets : [subsets]).filter(Boolean) + const family = convertFamiliesToArray(families ?? {}) if (family.length < 1) { return false @@ -19,150 +18,85 @@ export function constructURL ({ families, display, subsets, text }: GoogleFonts query.display = display } - if (subset.length > 0) { - query.subset = subset.join(',') + if (_subsets.length > 0) { + query.subset = _subsets.join(',') } if (text) { query.text = text } - return withHttps(withQuery(resolveURL(GOOGLE_FONTS_DOMAIN, prefix), query)) + return withHttps(withQuery(resolveURL(GOOGLE_FONTS_DOMAIN, 'css2'), query)) } -function convertFamiliesToArray (families: Families, v2 = true): string[] { +function convertFamiliesToArray (families: Families): string[] { const result: string[] = [] - // v1 - if (!v2) { - Object.entries(families).forEach(([name, values]) => { - if (!name) { - return - } - - name = parseFamilyName(name) - - if ((Array.isArray(values) && values.length > 0) || (values === true || values === 400)) { - result.push(name) - return - } - - if (values === 700) { - result.push(`${name}:bold`) - return - } - - if (Object.keys(values).length > 0) { - const styles: string[] = [] - - Object - .entries(values) - .sort(([styleA], [styleB]) => styleA.localeCompare(styleB)) - .forEach(([style, weight]) => { - const styleParsed = parseStyle(style) - - if (styleParsed === 'ital' && (weight === 700 || (Array.isArray(weight) && weight.includes(700)))) { - styles.push('bolditalic') - - if (Array.isArray(weight) && weight.includes(400)) { - styles.push(styleParsed) - } - } else if (styleParsed === 'wght' && (weight === 700 || (Array.isArray(weight) && weight.includes(700)))) { - styles.push('bold') - - if (Array.isArray(weight) && weight.includes(400)) { - styles.push(styleParsed) + Object.entries(families).forEach(([name, values]) => { + if (!name) { + return + } + + name = parseFamilyName(name) + + if (typeof values === 'string' && String(values).includes('..')) { + result.push(`${name}:wght@${values}`) + return + } + + if (Array.isArray(values) && values.length > 0) { + result.push(`${name}:wght@${values.join(';')}`) + return + } + + if (Object.keys(values).length > 0) { + const styles: string[] = [] + const weights: string[] = [] + let forceWght = false + + Object + .entries(values) + .sort(([styleA], [styleB]) => styleA.localeCompare(styleB)) + .forEach(([style, weight]) => { + const styleParsed = parseStyle(style) + styles.push(styleParsed) + + const weightList = Array.isArray(weight) ? weight : [weight] + weightList.forEach((value: string | number) => { + if (Object.keys(values).length === 1 && styleParsed === 'wght') { + weights.push(String(value)) + } else { + const index = styleParsed === 'wght' ? 0 : 1 + + if ( + (value.toString() === 'true' || value === 1 || value === 400) && + Object.entries(values).length === 1 && weightList.length === 1 + ) { + weights.push(`${index}`) + } else if (value) { + forceWght = true + weights.push(`${index},${value}`) } - } else if (weight !== false) { - styles.push(styleParsed) } }) + }) - const stylesSortered = styles - .sort(([styleA], [styleB]) => styleA.localeCompare(styleB)) - .reverse() - .join(',') - - if (stylesSortered === 'wght') { - result.push(name) - return - } - - result.push(`${name}:${stylesSortered}`) - } - }) - - return result.length ? [result.join('|')] : result - } - - // v2 - if (v2) { - Object.entries(families).forEach(([name, values]) => { - if (!name) { - return + if (!styles.includes('wght') && forceWght) { + styles.push('wght') } - name = parseFamilyName(name) - - if (typeof values === 'string' && String(values).includes('..')) { - result.push(`${name}:wght@${values}`) - return - } + const weightsSortered = weights + .sort(([weightA], [weightB]) => weightA.localeCompare(weightB)) + .join(';') - if (Array.isArray(values) && values.length > 0) { - result.push(`${name}:wght@${values.join(';')}`) - return - } + result.push(`${name}:${styles.join(',')}@${weightsSortered}`) + return + } - if (Object.keys(values).length > 0) { - const styles: string[] = [] - const weights: string[] = [] - let forceWght = false - - Object - .entries(values) - .sort(([styleA], [styleB]) => styleA.localeCompare(styleB)) - .forEach(([style, weight]) => { - const styleParsed = parseStyle(style) - styles.push(styleParsed) - - const weightList = Array.isArray(weight) ? weight : [weight] - weightList.forEach((value: string | number) => { - if (Object.keys(values).length === 1 && styleParsed === 'wght') { - weights.push(String(value)) - } else { - const index = styleParsed === 'wght' ? 0 : 1 - - if ( - (value.toString() === 'true' || value === 1 || value === 400) && - Object.entries(values).length === 1 && weightList.length === 1 - ) { - weights.push(`${index}`) - } else if (value) { - forceWght = true - weights.push(`${index},${value}`) - } - } - }) - }) - - if (!styles.includes('wght') && forceWght) { - styles.push('wght') - } - - const weightsSortered = weights - .sort(([weightA], [weightB]) => weightA.localeCompare(weightB)) - .join(';') - - result.push(`${name}:${styles.join(',')}@${weightsSortered}`) - return - } - - if (values) { - result.push(name) - } - }) - } + if (values) { + result.push(name) + } + }) return result } diff --git a/src/downloader.ts b/src/downloader.ts index 08fd45c..45112a1 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -3,6 +3,7 @@ import { extname, posix, resolve, dirname } from 'node:path' import { ofetch } from 'ofetch' import { Hookable } from 'hookable' import { isValidURL } from './is-valid-url' +import { FontSubset } from './types' export interface FontInputOutput { inputFont: string @@ -83,10 +84,13 @@ export class Downloader extends Hookable { await this.callHook('download:start') + const { searchParams } = new URL(this.url) + const subsets = searchParams.get('subset') ? searchParams.get('subset')?.split(',') as FontSubset[] : undefined + // download css content await this.callHook('download-css:before', this.url) - const cssContent = await ofetch(this.url, { headers }) - const fontsFromCss = parseFontsFromCss(cssContent, fontsPath) + const _css = await ofetch(this.url, { headers }) + const { fonts: fontsFromCss, css: cssContent } = parseFontsFromCss(_css, fontsPath, subsets) await this.callHook('download-css:done', this.url, cssContent, fontsFromCss) // download fonts from css @@ -165,7 +169,11 @@ export class Downloader extends Hookable { } } -function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput[] { +function parseFontsFromCss (content: string, fontsPath: string, subsets?: FontSubset[]): { + fonts: FontInputOutput[] + css: string +} { + const css: string[] = [] const fonts: FontInputOutput[] = [] const re = { face: /\s*(?:\/\*\s*(.*?)\s*\*\/)?[^@]*?@font-face\s*{(?:[^}]*?)}\s*/gi, @@ -178,23 +186,28 @@ function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput let match1 while ((match1 = re.face.exec(content)) !== null) { - const [fontface, comment] = match1 + const [fontface, subset] = match1 const familyRegExpArray = re.family.exec(fontface) const family = familyRegExpArray ? familyRegExpArray[1] : '' const weightRegExpArray = re.weight.exec(fontface) const weight = weightRegExpArray ? weightRegExpArray[1] : '' + if (subsets && subsets.length && !subsets.includes(subset as FontSubset)) { + continue + } + + css.push(fontface) + let match2 while ((match2 = re.url.exec(fontface)) !== null) { const [forReplace, url] = match2 const ext = extname(url).replace(/^\./, '') || 'woff2' const newFilename = formatFontFileName('{family}-{weight}-{i}.{ext}', { - comment: comment || '', family: family.replace(/\s+/g, '_'), weight: weight.replace(/\s+/g, '_') || '', - ext, - i: String(i++) + i: String(i++), + ext }).replace(/\.$/, '') fonts.push({ @@ -206,7 +219,10 @@ function parseFontsFromCss (content: string, fontsPath: string): FontInputOutput } } - return fonts + return { + css: css.join('\n'), + fonts + } } function formatFontFileName (template: string, values: { [s: string]: string } | ArrayLike): string { diff --git a/src/parse.ts b/src/parse.ts index 294d656..08d6d2c 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,4 +1,3 @@ -import { createURL } from 'ufo' import { isValidURL } from './is-valid-url' import { isValidDisplay, parseFamilyName, parseStyle } from './utils' import type { GoogleFonts, Families, FontDisplay, FontSubset } from './types' @@ -10,7 +9,7 @@ export function parse (url: string): GoogleFonts { return result } - const { searchParams, pathname } = createURL(url) + const { searchParams, pathname } = new URL(url) if (!searchParams.has('family')) { return result diff --git a/test/construct-url.test.ts b/test/construct-url.test.ts index 4ce4125..f33b6ba 100644 --- a/test/construct-url.test.ts +++ b/test/construct-url.test.ts @@ -2,7 +2,13 @@ import { describe, test, expect } from 'vitest' import { constructURL } from '../src' describe('constructURL', () => { - test('construct url v2', () => { + test('single family', () => { + expect(constructURL({ + families: { Roboto: true } + })).toEqual('https://fonts.googleapis.com/css2?family=Roboto') + }) + + test('multiple families', () => { expect(constructURL({ families: { 'Droid+Serif': true, @@ -10,13 +16,9 @@ describe('constructURL', () => { 'Roboto%2BMono': true } })).toEqual('https://fonts.googleapis.com/css2?family=Droid+Serif&family=Open+Sans&family=Roboto+Mono') + }) - expect(constructURL({ - families: { - Roboto: [100, 300, 400] - } - })).toEqual('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400') - + test('single style', () => { expect(constructURL({ families: { Roboto: { @@ -48,56 +50,9 @@ describe('constructURL', () => { } } })).toEqual('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700') + }) - expect(constructURL({ - families: { - Roboto: { - wght: [300, 400, 700], - ital: [400] - } - } - })).toEqual('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,700;1,400') - - expect(constructURL({ - families: { - 'Crimson Pro': '200..400' - } - })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400') - - expect(constructURL({ - families: { - 'Crimson Pro': { - wght: '200..400' - } - } - })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400') - - expect(constructURL({ - families: { - 'Crimson Pro': { - italic: '200..400' - } - } - })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@1,200..400') - - expect(constructURL({ - families: { - 'Crimson Pro': { - wght: '200..900', - italic: '200..700' - } - } - })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..700') - - expect(constructURL({ - families: { Roboto: true } - })).toEqual('https://fonts.googleapis.com/css2?family=Roboto') - - expect(constructURL({ - families: { Roboto: true }, - text: 'Foo Bar' - })).toEqual('https://fonts.googleapis.com/css2?family=Roboto&text=Foo+Bar') - + test('mutiple families and styles', () => { expect(constructURL({ families: { '': true, @@ -127,132 +82,60 @@ describe('constructURL', () => { })).toEqual('https://fonts.googleapis.com/css2?family=Roboto&family=Lato:wght@100&family=Raleway:ital,wght@0,400;1,100;1,400') }) - test('construct url v1', () => { - expect(constructURL({ - families: { Roboto: true }, - display: 'swap', - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Roboto&display=swap&subset=cyrillic') - - expect(constructURL({ - families: { Roboto: true, Lato: [100] }, - // @ts-ignore - subsets: ['foo', 'bar'] - })).toEqual('https://fonts.googleapis.com/css?family=Roboto|Lato&subset=foo,bar') - - expect(constructURL({ - families: { - Roboto: true - }, - display: 'swap', - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Roboto&display=swap&subset=cyrillic') - + test('multiple styles', () => { expect(constructURL({ families: { - Cantarell: { - ital: true - }, - 'Droid Serif': { - wght: [700] - } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Cantarell:ital|Droid+Serif:bold&subset=cyrillic') - - expect(constructURL({ - families: { - Cantarell: { - ital: [700] - }, - 'Droid Serif': { - wght: [700] - } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Cantarell:bolditalic|Droid+Serif:bold&subset=cyrillic') - - expect(constructURL({ - families: { - 'Droid Serif': { - wght: true, - ital: 100 - } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif:wght,ital&subset=cyrillic') - - expect(constructURL({ - families: { - 'Droid Serif': { - ital: 700, - wght: true - } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif:wght,bolditalic&subset=cyrillic') - + Roboto: [100, 300, 400] + } + })).toEqual('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400') expect(constructURL({ families: { - 'Droid Serif': { - ital: false, - wght: true + Roboto: { + wght: [300, 400, 700], + ital: [400] } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif&subset=cyrillic') + } + })).toEqual('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,700;1,400') + }) + test('axis', () => { expect(constructURL({ families: { - Cantarell: true, - 'Droid Serif': 400 - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Cantarell|Droid+Serif&subset=cyrillic') + 'Crimson Pro': '200..400' + } + })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400') expect(constructURL({ families: { - 'Droid Serif': { - ital: [400, 600, 700, 800] - }, - Roboto: { - wght: [400, 700] + 'Crimson Pro': { + wght: '200..400' } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif:ital,bolditalic|Roboto:wght,bold&subset=cyrillic') + } + })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..400') expect(constructURL({ families: { - 'Droid Serif': { - italic: [400, 600, 700, 800] - }, - Roboto: { - normal: [400, 700] + 'Crimson Pro': { + italic: '200..400' } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif:ital,bolditalic|Roboto:wght,bold&subset=cyrillic') + } + })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@1,200..400') expect(constructURL({ families: { - 'Droid Serif': { - i: [400, 600, 700, 800] - }, - Roboto: { - regular: [400, 700] + 'Crimson Pro': { + wght: '200..900', + italic: '200..700' } - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Droid+Serif:ital,bolditalic|Roboto:wght,bold&subset=cyrillic') + } + })).toEqual('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..700') + }) + test('text', () => { expect(constructURL({ - families: { - '': true, - Roboto: 700 - }, - subsets: 'cyrillic' - })).toEqual('https://fonts.googleapis.com/css?family=Roboto:bold&subset=cyrillic') + families: { Roboto: true }, + text: 'Foo Bar' + })).toEqual('https://fonts.googleapis.com/css2?family=Roboto&text=Foo+Bar') }) test('invalid', () => { diff --git a/test/download.test.ts b/test/download.test.ts index 7ca9101..e735030 100644 --- a/test/download.test.ts +++ b/test/download.test.ts @@ -22,7 +22,7 @@ describe('download', () => { rmSync(outputDir, { recursive: true, force: true }) }, 60000) - test('with a variable fonts', async () => { + test('variable fonts', async () => { const outputDir = temporaryDirectory() const stylePath = 'font.css' const fontsDir = 'fonts' @@ -45,7 +45,26 @@ describe('download', () => { rmSync(outputDir, { recursive: true, force: true }) }, 60000) - test('with a text', async () => { + test('subset', async () => { + const outputDir = temporaryDirectory() + const stylePath = 'font.css' + const fontsDir = 'fonts' + + await download('https://fonts.googleapis.com/css2?family=Poppins&subset=latin', { + outputDir, + stylePath, + fontsDir + }).execute() + + expect(existsSync(join(outputDir, stylePath))).toBe(true) + expect(existsSync(join(outputDir, fontsDir))).toBe(true) + expect(existsSync(join(outputDir, fontsDir, 'Poppins-400-1.woff2'))).toBe(true) + expect(existsSync(join(outputDir, fontsDir, 'Poppins-400-2.woff2'))).toBe(false) + + rmSync(outputDir, { recursive: true, force: true }) + }, 60000) + + test('simple text', async () => { const outputDir = temporaryDirectory() const stylePath = 'font.css' const fontsDir = 'fonts' diff --git a/test/parse.test.ts b/test/parse.test.ts index 594fbcb..5541258 100644 --- a/test/parse.test.ts +++ b/test/parse.test.ts @@ -2,102 +2,117 @@ import { describe, test, expect } from 'vitest' import { parse } from '../src' describe('parse', () => { - test('v2', () => { + test('single family', () => { + expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif')).toStrictEqual({ + families: { + 'Droid Serif': true + } + }) + expect(parse('https://fonts.googleapis.com/css2?family=Roboto')).toStrictEqual({ families: { Roboto: true } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Inter')).toStrictEqual({ families: { - Roboto: true, - 'Roboto Mono': true + Inter: true } }) + }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,700;1,400')).toStrictEqual({ + test('multiple families', () => { + expect(parse('https://fonts.googleapis.com/css?family=Cantarell|Droid+Serif')).toStrictEqual({ families: { - Roboto: { - wght: [300, 400, 700], - ital: [400] - } + Cantarell: true, + 'Droid Serif': true } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@1,400')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css?family=Cantarell:italic|Droid+Serif:bold')).toStrictEqual({ families: { - Roboto: { - wght: true, - ital: [400] + Cantarell: { + ital: true + }, + 'Droid Serif': { + wght: 700 } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital@1')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto&family=Roboto+Mono')).toStrictEqual({ families: { - Roboto: { - ital: true - } + Roboto: true, + 'Roboto Mono': true } }) + }) - expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..900')).toStrictEqual({ + test('single style', () => { + expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif:bolditalic')).toStrictEqual({ families: { - 'Crimson Pro': { - wght: '200..900' + 'Droid Serif': { + ital: 700 } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@1,200..900')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto:100')).toStrictEqual({ families: { - 'Crimson Pro': { - wght: true, - ital: '200..900' + Roboto: { + wght: [100] } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..700')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto:100,300')).toStrictEqual({ families: { - 'Crimson Pro': { - wght: '200..900', - ital: '200..700' + Roboto: { + wght: [100, 300] } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto&display=swap')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital@1')).toStrictEqual({ families: { - Roboto: true - }, - display: 'swap' + Roboto: { + ital: true + } + } }) + }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto:100')).toStrictEqual({ + test('mutiple styles', () => { + expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif:wght,italic')).toStrictEqual({ families: { - Roboto: { - wght: [100] + 'Droid Serif': { + wght: true, + ital: true } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto:100,300')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,700;1,400')).toStrictEqual({ families: { Roboto: { - wght: [100, 300] + wght: [300, 400, 700], + ital: [400] } } }) - expect(parse('https://fonts.googleapis.com/css2?family=Roboto&text=Foo%20Bar')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@1,400')).toStrictEqual({ families: { - Roboto: true - }, - text: 'Foo Bar' + Roboto: { + wght: true, + ital: [400] + } + } }) + }) + test('mutiple families and styles', () => { expect(parse('https://fonts.googleapis.com/css2?family=Roboto&family=Lato:wght@100;400&family=Raleway:ital,wght@0,400;1,100;1,400')).toStrictEqual({ families: { Roboto: true, @@ -112,54 +127,49 @@ describe('parse', () => { }) }) - test('v1', () => { - expect(parse('https://fonts.googleapis.com/css?family=Roboto&family=Lato:&display=swap&subset=cyrillic')).toStrictEqual({ - families: { - Roboto: true - }, - display: 'swap', - subsets: ['cyrillic'] - }) - - expect(parse('https://fonts.googleapis.com/css?family=Cantarell:italic|Droid+Serif:bold')).toStrictEqual({ + test('axis', () => { + expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@200..900')).toStrictEqual({ families: { - Cantarell: { - ital: true - }, - 'Droid Serif': { - wght: 700 + 'Crimson Pro': { + wght: '200..900' } } }) - expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif:wght,italic')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@1,200..900')).toStrictEqual({ families: { - 'Droid Serif': { + 'Crimson Pro': { wght: true, - ital: true + ital: '200..900' } } }) - expect(parse('https://fonts.googleapis.com/css?family=Cantarell|Droid+Serif')).toStrictEqual({ + expect(parse('https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..700')).toStrictEqual({ families: { - Cantarell: true, - 'Droid Serif': true + 'Crimson Pro': { + wght: '200..900', + ital: '200..700' + } } }) + }) - expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif')).toStrictEqual({ + test('display', () => { + expect(parse('https://fonts.googleapis.com/css2?family=Roboto&display=swap')).toStrictEqual({ families: { - 'Droid Serif': true - } + Roboto: true + }, + display: 'swap' }) + }) - expect(parse('https://fonts.googleapis.com/css?family=Droid+Serif:bolditalic')).toStrictEqual({ + test('text', () => { + expect(parse('https://fonts.googleapis.com/css2?family=Roboto&text=Foo%20Bar')).toStrictEqual({ families: { - 'Droid Serif': { - ital: 700 - } - } + Roboto: true + }, + text: 'Foo Bar' }) })