From 5bab286a8691bb6ca172b2494347c8ce93c70261 Mon Sep 17 00:00:00 2001 From: Tony Ross Date: Thu, 12 Sep 2019 23:01:45 -0500 Subject: [PATCH] New: Add helpers to get browser names and support details Ref #2896 --- packages/utils/README.md | 3 + .../utils/scripts/mdn-browser-compat-data.js | 6 +- packages/utils/src/compat/browsers.ts | 104 ++++++++++++++---- packages/utils/src/compat/cache.ts | 2 +- packages/utils/src/compat/css.ts | 32 ++++-- packages/utils/src/compat/html.ts | 4 +- packages/utils/src/compat/support.ts | 38 +++++-- packages/utils/tests/compat/browsers.ts | 72 +++++++++--- 8 files changed, 194 insertions(+), 67 deletions(-) diff --git a/packages/utils/README.md b/packages/utils/README.md index 86b8e2c070f..c406959717d 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -33,7 +33,10 @@ hints inside the extended configurations. ### compat +* `getFriendlyName`: Get the friendly name of a browser from an id. * `getUnsupported`: Get browsers without support for CSS or HTML features. +* `getUnsupportedDetails`: Get browsers without support with details on + when support was added or removed. * `isSupported`: Query MDN for support of CSS or HTML features. ### configStore diff --git a/packages/utils/scripts/mdn-browser-compat-data.js b/packages/utils/scripts/mdn-browser-compat-data.js index db1ace65954..8f365413bc4 100644 --- a/packages/utils/scripts/mdn-browser-compat-data.js +++ b/packages/utils/scripts/mdn-browser-compat-data.js @@ -233,7 +233,7 @@ const removeFeatures = (data) => { */ const removeBrowserDetails = (browsers) => { for (const browserName of Object.keys(browsers)) { - browsers[browserName] = /** @type {any} */({}); + browsers[browserName] = /** @type {any} */({ name: browsers[browserName].name }); } }; @@ -251,10 +251,10 @@ removeFeatures(data.css); removeFeatures(data.html); const code = `/* eslint-disable */ -import { PrimaryIdentifier } from 'mdn-browser-compat-data/types'; +import { Browsers, PrimaryIdentifier } from 'mdn-browser-compat-data/types'; type Data = { - browsers: PrimaryIdentifier; + browsers: Browsers; css: PrimaryIdentifier; html: PrimaryIdentifier; } diff --git a/packages/utils/src/compat/browsers.ts b/packages/utils/src/compat/browsers.ts index 79232fc3ce6..12c203c72da 100644 --- a/packages/utils/src/compat/browsers.ts +++ b/packages/utils/src/compat/browsers.ts @@ -1,7 +1,17 @@ import { Identifier, SimpleSupportStatement, SupportStatement } from 'mdn-browser-compat-data/types'; import * as semver from 'semver'; -export type UnsupportedBrowsers = string[] | null; +import { mdn } from './browser-compat-data'; + +export type SupportDetails = { + versionAdded?: string; + versionRemoved?: string; +}; + +export type UnsupportedBrowsers = { + browsers: string[]; + details: Map; +}; const enum Support { No, @@ -9,6 +19,10 @@ const enum Support { Unknown } +type SupportStatus = SupportDetails & { + support: Support; +}; + // Map `browserslist` browser names to MDN ones. const browserToMDN = new Map([ ['and_chr', 'chrome_android'], @@ -32,30 +46,34 @@ const coerce = (version: string): string | semver.SemVer => { return semver.coerce(version) || version; }; +const normalizeBrowserName = (name: string) => { + return browserToMDN.get(name) || name; +}; + /** * Intepret if the provided statement indicates support for the given browser version. */ -const isSupported = (support: SimpleSupportStatement, prefix: string, rawVersion: string): Support => { +const isSupported = (support: SimpleSupportStatement, prefix: string, rawVersion: string): SupportStatus => { const version = coerce(rawVersion); // Ignore support that requires users to enable a flag. if (support.flags) { - return Support.Unknown; + return { support: Support.Unknown }; } // If feature doesn't match the same prefix, then it's not supported. if (prefix !== (support.prefix || '')) { - return Support.No; + return { support: Support.No }; } // If feature was never added, then it's not supported. if (support.version_added === false) { - return Support.No; + return { support: Support.No }; } // If a feature was removed before the target version, it's not supported. if (typeof support.version_removed === 'string' && semver.lte(coerce(support.version_removed), version)) { - return Support.No; + return { support: Support.No, versionRemoved: support.version_removed }; } /* @@ -67,33 +85,41 @@ const isSupported = (support: SimpleSupportStatement, prefix: string, rawVersion * https://github.com/mdn/browser-compat-data/issues/3021 */ if (support.version_added === true) { - return Support.Yes; + return { support: Support.Yes }; } // If feature was added by the target version, it's supported; if after it's not. if (typeof support.version_added === 'string') { - return semver.lte(coerce(support.version_added), version) ? Support.Yes : Support.No; + if (semver.lte(coerce(support.version_added), version)) { + return { support: Support.Yes }; + } + + return { support: Support.No, versionAdded: support.version_added }; } // Ignore all other cases (e.g. if a feature was removed but we don't know when). - return Support.Unknown; + return { support: Support.Unknown }; }; /** - * Interpret if the provided support statements indicate the given browser version in supported. + * Interpret if the provided support statements indicate the given browser version is supported. */ -const isBrowserUnsupported = (support: SupportStatement, prefix: string, version: string): boolean => { +const isBrowserSupported = (support: SupportStatement, prefix: string, version: string): SupportStatus => { // Convert single entries to an array for consistent handling. const browserSupport = Array.isArray(support) ? support : [support]; - let status = Support.Unknown; + const details: SupportStatus = { support: Support.Unknown }; // Items are listed from newest to oldest. The first clear rule wins. for (const simpleSupport of browserSupport) { - switch (isSupported(simpleSupport, prefix, version)) { + const status = isSupported(simpleSupport, prefix, version); + + switch (status.support) { case Support.Yes: - return false; + return { support: Support.Yes }; case Support.No: - status = Support.No; + details.support = Support.No; + details.versionAdded = status.versionAdded || details.versionAdded; + details.versionRemoved = status.versionRemoved || details.versionRemoved; break; // Keep looking in case a feature was temporarily removed or is prefixed. case Support.Unknown: default: @@ -101,7 +127,22 @@ const isBrowserUnsupported = (support: SupportStatement, prefix: string, version } } - return status === Support.No; + if (details.support === Support.Unknown) { + details.support = Support.Yes; + } + + return details; +}; + +/** + * Retrieve the friendly name of the provided browser + * (e.g. "Internet Explorer" for "ie"). + */ +export const getFriendlyName = (browser: string): string => { + const [name] = browser.split(' '); + const data = mdn.browsers[normalizeBrowserName(name)]; + + return data.name; }; /** @@ -112,30 +153,45 @@ const isBrowserUnsupported = (support: SupportStatement, prefix: string, version * @param feature An MDN feature `Identifier` with `__compat` data. * @param browsers A list of target browsers (e.g. `['chrome 74', 'ie 11']`). */ -export const getUnsupportedBrowsers = (feature: Identifier | undefined, prefix: string, browsers: string[]): UnsupportedBrowsers => { +export const getUnsupportedBrowsers = (feature: Identifier | undefined, prefix: string, browsers: string[]): UnsupportedBrowsers | null => { if (!feature || !feature.__compat) { return null; // Assume support if no matching feature was provided. } const support = feature.__compat.support; const unsupported: string[] = []; + const details = new Map(); for (const browser of browsers) { const [name, versionStr] = browser.split(' '); - const mdnBrowser = browserToMDN.get(name)!; + const mdnBrowser = normalizeBrowserName(name); const browserSupport = support[mdnBrowser]; const versions = versionStr.split('-'); // Handle 'android 4.4.3-4.4.4'. - if (browserSupport) { - const isUnsupported = versions.some((version) => { - return isBrowserUnsupported(browserSupport, prefix, version); - }); + if (!browserSupport) { + continue; + } + + for (const version of versions) { + const status = isBrowserSupported(browserSupport, prefix, version); + + if (status.support === Support.No) { + const supportDetails: SupportDetails = {}; - if (isUnsupported) { + if (status.versionAdded) { + supportDetails.versionAdded = status.versionAdded; + } + + if (status.versionRemoved) { + supportDetails.versionRemoved = status.versionRemoved; + } + + details.set(browser, supportDetails); unsupported.push(browser); + break; } } } - return unsupported.length ? unsupported : null; + return unsupported.length ? { browsers: unsupported, details } : null; }; diff --git a/packages/utils/src/compat/cache.ts b/packages/utils/src/compat/cache.ts index eda1666668b..8056f27791d 100644 --- a/packages/utils/src/compat/cache.ts +++ b/packages/utils/src/compat/cache.ts @@ -1,4 +1,4 @@ -type Value = string[] | null; +type Value = any | null; const cache = new Map>(); diff --git a/packages/utils/src/compat/css.ts b/packages/utils/src/compat/css.ts index f7dda4e0c48..a17768ded1a 100644 --- a/packages/utils/src/compat/css.ts +++ b/packages/utils/src/compat/css.ts @@ -53,7 +53,7 @@ const getTokens = (nodes: any[]): [string, string][] => { * sub-features in the provided context, using `matches` data to test * each tokenized string from the value. */ -const getPartialValueUnsupported = (context: Identifier, value: string, browsers: string[]): UnsupportedBrowsers => { +const getPartialValueUnsupported = (context: Identifier, value: string, browsers: string[]): UnsupportedBrowsers | null => { const prefix = vendor.prefix(value); const unprefixedValue = vendor.unprefixed(value); const tokens = getTokens(valueParser(value).nodes); @@ -96,7 +96,7 @@ const getPartialValueUnsupported = (context: Identifier, value: string, browsers * Determine if the provided CSS value is supported, first by looking for an * exact match for the full value, falling back to search for a partial match. */ -const getValueUnsupported = (context: Identifier, value: string, browsers: string[]): UnsupportedBrowsers => { +const getValueUnsupported = (context: Identifier, value: string, browsers: string[]): UnsupportedBrowsers | null => { const [data, prefix] = getFeatureData(context, value); if (data) { @@ -110,7 +110,7 @@ const getValueUnsupported = (context: Identifier, value: string, browsers: strin * Determine if the provided CSS declaration consisting of a property * and optionally a value is supported (e.g. `border-radius` or `display: grid`). */ -export const getDeclarationUnsupported = (feature: DeclarationQuery, browsers: string[]): UnsupportedBrowsers => { +export const getDeclarationUnsupported = (feature: DeclarationQuery, browsers: string[]): UnsupportedBrowsers | null => { const key = `css-declaration:${feature.property}|${feature.value || ''}`; return getCachedValue(key, browsers, () => { @@ -127,7 +127,7 @@ export const getDeclarationUnsupported = (feature: DeclarationQuery, browsers: s /** * Determine if the provided CSS at-rule is supported (e.g. `keyframes`). */ -export const getRuleUnsupported = (feature: RuleQuery, browsers: string[]): UnsupportedBrowsers => { +export const getRuleUnsupported = (feature: RuleQuery, browsers: string[]): UnsupportedBrowsers | null => { return getCachedValue(`css-rule:${feature.rule}`, browsers, () => { const [data, prefix] = getFeatureData(mdn.css['at-rules'], feature.rule); @@ -135,7 +135,7 @@ export const getRuleUnsupported = (feature: RuleQuery, browsers: string[]): Unsu }); }; -const getPseudoSelectorUnsupported = (value: string, browsers: string[]): UnsupportedBrowsers => { +const getPseudoSelectorUnsupported = (value: string, browsers: string[]): UnsupportedBrowsers | null => { const name = value.replace(/^::?/, ''); // Strip leading `:` or `::`. return getCachedValue(`css-pseudo-selector:${name}`, browsers, () => { @@ -153,20 +153,34 @@ const getPseudoSelectorUnsupported = (value: string, browsers: string[]): Unsupp * special cases (e.g. newer attribute and combinator selectors) need special * handling to map to the MDN data (which hasn't been done yet). */ -export const getSelectorUnsupported = (feature: SelectorQuery, browsers: string[]): UnsupportedBrowsers => { +export const getSelectorUnsupported = (feature: SelectorQuery, browsers: string[]): UnsupportedBrowsers | null => { const parser = selectorParser(); const root = parser.astSync(feature.selector); // eslint-disable-line no-sync - let unsupported: string[] = []; + const unsupported: UnsupportedBrowsers = { + browsers: [], + details: new Map() + }; // https://github.com/postcss/postcss-selector-parser/blob/master/API.md#containerwalk-proxies root.walkPseudos((node: { value: string }) => { const result = getPseudoSelectorUnsupported(node.value, browsers); if (result) { - unsupported = [...unsupported, ...result]; + unsupported.browsers = [...unsupported.browsers, ...result.browsers]; + + /* + * Note: Details can be incorrect if multiple parts of a selector + * are unsupported. Currently details will be set based on the + * last part of the selector which was unsupported. + * + * TODO: Fix by requiring callers to parse the selector instead. + */ + for (const [browser, details] of result.details) { + unsupported.details.set(browser, details); + } } }); - return unsupported.length ? unsupported : null; + return unsupported.browsers.length ? unsupported : null; }; diff --git a/packages/utils/src/compat/html.ts b/packages/utils/src/compat/html.ts index 640b59df9b4..87c36d8aff0 100644 --- a/packages/utils/src/compat/html.ts +++ b/packages/utils/src/compat/html.ts @@ -20,7 +20,7 @@ export type ElementQuery = { * a context element and/or value is supported (e.g. `{ attribute: 'hidden' }` * or `{ attribute: 'rel', element: 'link', value: 'stylesheet' }`). */ -export const getAttributeUnsupported = (feature: AttributeQuery, browsers: string[]): UnsupportedBrowsers => { +export const getAttributeUnsupported = (feature: AttributeQuery, browsers: string[]): UnsupportedBrowsers | null => { const key = `html-attribute:${feature.element || ''}|${feature.attribute}|${feature.value || ''}`; return getCachedValue(key, browsers, () => { @@ -56,7 +56,7 @@ export const getAttributeUnsupported = (feature: AttributeQuery, browsers: strin /** * Determine if the provided HTML element is supported (e.g. `details`). */ -export const getElementUnsupported = (feature: ElementQuery, browsers: string[]): UnsupportedBrowsers => { +export const getElementUnsupported = (feature: ElementQuery, browsers: string[]): UnsupportedBrowsers | null => { return getCachedValue(`html-element:${feature.element}`, browsers, () => { const [data, prefix] = getFeatureData(mdn.html.elements, feature.element); diff --git a/packages/utils/src/compat/support.ts b/packages/utils/src/compat/support.ts index 01af1be2174..022d003990d 100644 --- a/packages/utils/src/compat/support.ts +++ b/packages/utils/src/compat/support.ts @@ -16,21 +16,19 @@ import { ElementQuery } from './html'; +export { + getFriendlyName, + UnsupportedBrowsers +} from './browsers'; + export type FeatureQuery = AttributeQuery | DeclarationQuery | ElementQuery | RuleQuery | SelectorQuery; /** - * ```js - * getUnsupported({ element: 'details' }, ['chrome 74', 'ie 11']); // ['ie 11'] - * getUnsupported({ attribute: 'hidden' }, browsers); - * getUnsupported({ attribute: 'rel', element: 'link', value: 'noopener' }, ['edge 12', 'firefox 63']); // ['edge 12'] - * getUnsupported({ property: 'border-radius' }, browsers); - * getUnsupported({ property: 'color', value: '#00FF00FF' }, browsers); - * getUnsupported({ property: 'transform', value: 'translate3d(0, 0, 10px)' }, browsers); - * getUnsupported({ rule: '@supports' }, browsers); - * getUnsupported({ selector: 'input:invalid' }, browsers); - * ``` + * Similar to `getUnsupported`, but returns an object with both a list of + * `browsers` which were unsupported and a map of browsers to `browserDetails` + * to get additional information (e.g. what version the feature is added in). */ -export const getUnsupported = (feature: FeatureQuery, browsers: string[]): UnsupportedBrowsers => { +export const getUnsupportedDetails = (feature: FeatureQuery, browsers: string[]): UnsupportedBrowsers | null => { if ('attribute' in feature) { return getAttributeUnsupported(feature, browsers); } else if ('element' in feature) { @@ -44,6 +42,24 @@ export const getUnsupported = (feature: FeatureQuery, browsers: string[]): Unsup return getSelectorUnsupported(feature, browsers); }; +/** + * ```js + * getUnsupported({ element: 'details' }, ['chrome 74', 'ie 11']); // ['ie 11'] + * getUnsupported({ attribute: 'hidden' }, browsers); + * getUnsupported({ attribute: 'rel', element: 'link', value: 'noopener' }, ['edge 12', 'firefox 63']); // ['edge 12'] + * getUnsupported({ property: 'border-radius' }, browsers); + * getUnsupported({ property: 'color', value: '#00FF00FF' }, browsers); + * getUnsupported({ property: 'transform', value: 'translate3d(0, 0, 10px)' }, browsers); + * getUnsupported({ rule: '@supports' }, browsers); + * getUnsupported({ selector: 'input:invalid' }, browsers); + * ``` + */ +export const getUnsupported = (feature: FeatureQuery, browsers: string[]): string[] | null => { + const data = getUnsupportedDetails(feature, browsers); + + return data && data.browsers; +}; + export const getSupported = (feature: FeatureQuery, browsers: string[]): string[] | null => { const unsupported = getUnsupported(feature, browsers); diff --git a/packages/utils/tests/compat/browsers.ts b/packages/utils/tests/compat/browsers.ts index 89c5c2966e5..5da0e93a5ee 100644 --- a/packages/utils/tests/compat/browsers.ts +++ b/packages/utils/tests/compat/browsers.ts @@ -31,20 +31,20 @@ test('Handles complex support', (t) => { } as any; /* eslint-enable */ - t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 12']), ['opera 12'], 'Before first unprefixed support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 12'])!.browsers, ['opera 12'], 'Before first unprefixed support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 12.1']), null, 'At first unprefixed support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 13']), null, 'During first unprefixed support'); - t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 15']), ['opera 15'], 'After first unprefixed support'); - t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 29']), ['opera 29'], 'Before second unprefixed support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 15'])!.browsers, ['opera 15'], 'After first unprefixed support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 29'])!.browsers, ['opera 29'], 'Before second unprefixed support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 30']), null, 'At second unprefixed support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 31']), null, 'After second unprefixed support'); - t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 11']), ['opera 11'], 'Before -o- support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 11'])!.browsers, ['opera 11'], 'Before -o- support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 12']), null, 'At -o- support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 12']), null, 'During -o- support'); - t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 15']), ['opera 15'], 'After -o- support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 15'])!.browsers, ['opera 15'], 'After -o- support'); - t.deepEqual(getUnsupportedBrowsers(keyframes, '-webkit-', ['opera 14']), ['opera 14'], 'Before -webkit- support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '-webkit-', ['opera 14'])!.browsers, ['opera 14'], 'Before -webkit- support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '-webkit-', ['opera 15']), null, 'At -webkit- support'); t.deepEqual(getUnsupportedBrowsers(keyframes, '-webkit-', ['opera 16']), null, 'During -webkit- support'); }); @@ -67,14 +67,14 @@ test('Handles supported prefix', (t) => { /* eslint-enable */ t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 66']), null); - t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 65']), ['firefox 65']); - t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 41']), ['firefox 41']); - t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 40']), ['firefox 40']); + t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 65'])!.browsers, ['firefox 65']); + t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 41'])!.browsers, ['firefox 41']); + t.deepEqual(getUnsupportedBrowsers(maxContent, '', ['firefox 40'])!.browsers, ['firefox 40']); t.deepEqual(getUnsupportedBrowsers(maxContent, '-moz-', ['firefox 66']), null); t.deepEqual(getUnsupportedBrowsers(maxContent, '-moz-', ['firefox 65']), null); t.deepEqual(getUnsupportedBrowsers(maxContent, '-moz-', ['firefox 41']), null); - t.deepEqual(getUnsupportedBrowsers(maxContent, '-moz-', ['firefox 40']), ['firefox 40']); + t.deepEqual(getUnsupportedBrowsers(maxContent, '-moz-', ['firefox 40'])!.browsers, ['firefox 40']); }); test('Handles unsupported prefix', (t) => { @@ -91,8 +91,8 @@ test('Handles unsupported prefix', (t) => { } as any; /* eslint-enable */ - t.deepEqual(getUnsupportedBrowsers(appearance, '', ['firefox 1']), ['firefox 1']); - t.deepEqual(getUnsupportedBrowsers(appearance, '-webkit-', ['firefox 1']), ['firefox 1']); + t.deepEqual(getUnsupportedBrowsers(appearance, '', ['firefox 1'])!.browsers, ['firefox 1']); + t.deepEqual(getUnsupportedBrowsers(appearance, '-webkit-', ['firefox 1'])!.browsers, ['firefox 1']); t.deepEqual(getUnsupportedBrowsers(appearance, '-moz-', ['firefox 1']), null); }); @@ -116,11 +116,11 @@ test('Handles multiple supported prefixes', (t) => { } as any; /* eslint-enable*/ - t.deepEqual(getUnsupportedBrowsers(boxFlex, '', ['firefox 48']), ['firefox 48']); + t.deepEqual(getUnsupportedBrowsers(boxFlex, '', ['firefox 48'])!.browsers, ['firefox 48']); t.deepEqual(getUnsupportedBrowsers(boxFlex, '-moz-', ['firefox 48']), null); - t.deepEqual(getUnsupportedBrowsers(boxFlex, '-webkit-', ['firefox 48']), ['firefox 48']); + t.deepEqual(getUnsupportedBrowsers(boxFlex, '-webkit-', ['firefox 48'])!.browsers, ['firefox 48']); - t.deepEqual(getUnsupportedBrowsers(boxFlex, '', ['firefox 49']), ['firefox 49']); + t.deepEqual(getUnsupportedBrowsers(boxFlex, '', ['firefox 49'])!.browsers, ['firefox 49']); t.deepEqual(getUnsupportedBrowsers(boxFlex, '-moz-', ['firefox 49']), null); t.deepEqual(getUnsupportedBrowsers(boxFlex, '-webkit-', ['firefox 49']), null); }); @@ -141,6 +141,44 @@ test('Handles removed features', (t) => { /* eslint-enable */ t.deepEqual(getUnsupportedBrowsers(boxLines, '-webkit-', ['chrome 66']), null); - t.deepEqual(getUnsupportedBrowsers(boxLines, '-webkit-', ['chrome 67']), ['chrome 67']); - t.deepEqual(getUnsupportedBrowsers(boxLines, '-webkit-', ['chrome 66', 'chrome 67']), ['chrome 67']); + t.deepEqual(getUnsupportedBrowsers(boxLines, '-webkit-', ['chrome 67'])!.browsers, ['chrome 67']); + t.deepEqual(getUnsupportedBrowsers(boxLines, '-webkit-', ['chrome 66', 'chrome 67'])!.browsers, ['chrome 67']); +}); + +test('Includes accurate details', (t) => { + /* eslint-disable */ + const keyframes: Identifier = { + __compat: { + support: { + opera: [ + { + version_added: "30" + }, + { + prefix: "-webkit-", + version_added: "15" + }, + { + version_added: "12.1", + version_removed: "15" + }, + { + prefix: "-o-", + version_added: "12", + version_removed: "15" + } + ] + } + } + } as any; + /* eslint-enable */ + + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 12'])!.details.get('opera 12'), { versionAdded: '12.1' }, 'Before first unprefixed support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 15'])!.details.get('opera 15'), { versionAdded: '30', versionRemoved: '15' }, 'After first unprefixed support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '', ['opera 29'])!.details.get('opera 29'), { versionAdded: '30', versionRemoved: '15' }, 'Before second unprefixed support'); + + t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 11'])!.details.get('opera 11'), { versionAdded: '12' }, 'Before -o- support'); + t.deepEqual(getUnsupportedBrowsers(keyframes, '-o-', ['opera 15'])!.details.get('opera 15'), { versionRemoved: '15' }, 'After -o- support'); + + t.deepEqual(getUnsupportedBrowsers(keyframes, '-webkit-', ['opera 14'])!.details.get('opera 14'), { versionAdded: '15' }, 'Before -webkit- support'); });